react_on_rails 16.2.0.beta.3 → 16.2.0.beta.4
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 +18 -0
- data/CLAUDE.md +59 -0
- data/CONTRIBUTING.md +48 -0
- data/Gemfile.development_dependencies +1 -0
- data/Gemfile.lock +25 -2
- data/SWITCHING_CI_CONFIGS.md +55 -6
- data/Steepfile +51 -0
- data/bin/ci-rerun-failures +34 -11
- data/bin/ci-run-failed-specs +25 -1
- data/bin/ci-switch-config +254 -32
- 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 +1 -5
- data/lib/react_on_rails/configuration.rb +56 -12
- data/lib/react_on_rails/controller.rb +3 -3
- data/lib/react_on_rails/doctor.rb +4 -2
- data/lib/react_on_rails/helper.rb +3 -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 +135 -29
- data/react_on_rails_pro/Gemfile.development_dependencies +1 -0
- data/react_on_rails_pro/Gemfile.lock +6 -3
- data/react_on_rails_pro/README.md +559 -38
- data/react_on_rails_pro/docs/installation.md +40 -22
- data/react_on_rails_pro/docs/node-renderer/basics.md +26 -19
- data/react_on_rails_pro/docs/node-renderer/js-configuration.md +24 -22
- data/react_on_rails_pro/docs/node-renderer/troubleshooting.md +2 -0
- data/react_on_rails_pro/lib/react_on_rails_pro/version.rb +1 -1
- data/react_on_rails_pro/package.json +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/rbs.rake +47 -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 +6 -3
- data/react_on_rails_pro/spec/dummy/client/node-renderer.js +1 -1
- data/react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb +16 -17
- 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 -3
- data/lib/react_on_rails/pro_utils.rb +0 -37
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Installation
|
|
2
|
+
|
|
2
3
|
Since the repository is private, you will get a **GitHub Personal Access Token** and an account that can access the packages. Substitute that value in the commands below. If you dont' have this, ask [justin@shakacode.com](mailto:justin@shakacode.com) to give you one.
|
|
3
4
|
|
|
4
5
|
Check the [CHANGELOG](https://github.com/shakacode/react_on_rails_pro/blob/master/CHANGELOG.md) to see what version you want.
|
|
@@ -8,13 +9,16 @@ Check the [CHANGELOG](https://github.com/shakacode/react_on_rails_pro/blob/maste
|
|
|
8
9
|
For the below docs, find the desired `<version>` in the CHANGELOG. Note, for pre-release versions, gems have all periods, and node packages uses a dash, like gem `3.0.0.rc.0` and node package `3.0.0-rc.0`.
|
|
9
10
|
|
|
10
11
|
# Ruby
|
|
12
|
+
|
|
11
13
|
## Gem Installation
|
|
14
|
+
|
|
12
15
|
1. Ensure your **Rails** app is using the **react_on_rails** gem, version greater than 11.0.7.
|
|
13
|
-
1. Add the `react_on_rails_pro` gem to your **Gemfile**. Substitute the appropriate version number.
|
|
14
|
-
|
|
16
|
+
1. Add the `react_on_rails_pro` gem to your **Gemfile**. Substitute the appropriate version number.
|
|
17
|
+
|
|
15
18
|
## Gemfile Change
|
|
16
19
|
|
|
17
20
|
Replace the following in the snippet for the Gemfile
|
|
21
|
+
|
|
18
22
|
1. `<account>` for the api key
|
|
19
23
|
2. `<api-key>`
|
|
20
24
|
3. `<version>` desired
|
|
@@ -33,6 +37,7 @@ source "https://rubygems.pkg.github.com/shakacode-tools" do
|
|
|
33
37
|
gem "react_on_rails_pro", "<version>"
|
|
34
38
|
end
|
|
35
39
|
```
|
|
40
|
+
|
|
36
41
|
Or use the `gem install` command:
|
|
37
42
|
|
|
38
43
|
```bash
|
|
@@ -46,23 +51,27 @@ bundle config set rubygems.pkg.github.com <username>:<token>
|
|
|
46
51
|
```
|
|
47
52
|
|
|
48
53
|
## Using a branch in your Gemfile
|
|
54
|
+
|
|
49
55
|
Note, you should probably use an ENV value for the token so that you don't check this into your source code.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
gem "react_on_rails_pro", version: "<version>", git: "https://[your-github-token]:x-oauth-basic@github.com/shakacode/react_on_rails_pro.git", tag: "<version>"
|
|
59
|
+
```
|
|
53
60
|
|
|
54
61
|
## Rails Configuration
|
|
55
|
-
|
|
62
|
+
|
|
63
|
+
You don't need to create an initializer if you are satisfied with the default as described in
|
|
56
64
|
[Configuration](./configuration.md)
|
|
57
65
|
|
|
58
66
|
# Node Package
|
|
67
|
+
|
|
59
68
|
Note, you only need to install the Node Package if you are using the standalone node renderer, `NodeRenderer`.
|
|
60
69
|
|
|
61
70
|
## Installation
|
|
62
71
|
|
|
63
72
|
1. Create a subdirectory of your rails project for the Node renderer. Let's use `react-on-rails-pro`.
|
|
64
|
-
|
|
65
73
|
2. Create a file `react-on-rails-pro/.npmrc` with the following
|
|
74
|
+
|
|
66
75
|
```
|
|
67
76
|
always-auth=true
|
|
68
77
|
//npm.pkg.github.com/:_authToken=<token>
|
|
@@ -70,6 +79,7 @@ always-auth=true
|
|
|
70
79
|
```
|
|
71
80
|
|
|
72
81
|
3. Create a `react-on-rails-pro/package.json`
|
|
82
|
+
|
|
73
83
|
```json
|
|
74
84
|
{
|
|
75
85
|
"private": true,
|
|
@@ -86,20 +96,18 @@ always-auth=true
|
|
|
86
96
|
|
|
87
97
|
If you really want to use yarn, see [Yarn can't find private Github npm registry](https://stackoverflow.com/questions/58316109/yarn-cant-find-private-github-npm-registry)
|
|
88
98
|
|
|
89
|
-
5. You can start the renderer with either the executable `node-renderer` or, preferably, with
|
|
99
|
+
5. You can start the renderer with either the executable `node-renderer` or, preferably, with
|
|
90
100
|
a startup JS file, say called `react-on-rails-pro/react-on-rails-pro-node-renderer.js` with
|
|
91
|
-
these contents. _Note the use of the namespaced **`@shakacode-tools/react-on-rails-pro-node-renderer`** for the package.
|
|
101
|
+
these contents. \_Note the use of the namespaced **`@shakacode-tools/react-on-rails-pro-node-renderer`** for the package.
|
|
92
102
|
|
|
93
103
|
```js
|
|
94
|
-
const path = require('path')
|
|
95
|
-
const {
|
|
96
|
-
reactOnRailsProNodeRenderer,
|
|
97
|
-
} = require('@shakacode-tools/react-on-rails-pro-node-renderer')
|
|
104
|
+
const path = require('path');
|
|
105
|
+
const { reactOnRailsProNodeRenderer } = require('@shakacode-tools/react-on-rails-pro-node-renderer');
|
|
98
106
|
|
|
99
|
-
const env = process.env
|
|
107
|
+
const env = process.env;
|
|
100
108
|
|
|
101
109
|
const config = {
|
|
102
|
-
|
|
110
|
+
serverBundleCachePath: path.resolve(__dirname, '../.node-renderer-bundles'),
|
|
103
111
|
|
|
104
112
|
// Listen at RENDERER_PORT env value or default port 3800
|
|
105
113
|
logLevel: env.RENDERER_LOG_LEVEL || 'debug', // show all logs
|
|
@@ -129,21 +137,25 @@ const config = {
|
|
|
129
137
|
// allWorkersRestartInterval: 15,
|
|
130
138
|
// time in minutes between each worker restarting when restarting all workers
|
|
131
139
|
// delayBetweenIndividualWorkerRestarts: 2,
|
|
132
|
-
|
|
140
|
+
// Also, you can set he parameter gracefulWorkerRestartTimeout to force the worker to restart
|
|
141
|
+
// If it's the time for the worker to restart, the worker waits until it serves all active requests before restarting
|
|
142
|
+
// If a worker stuck because of a memory leakage or an infinite loop, you can set a timeout that master waits for it before killing the worker
|
|
143
|
+
};
|
|
133
144
|
|
|
134
145
|
// Renderer detects a total number of CPUs on virtual hostings like Heroku
|
|
135
146
|
// or CircleCI instead of CPUs number allocated for current container. This
|
|
136
147
|
// results in spawning many workers while only 1-2 of them really needed.
|
|
137
148
|
if (env.CI) {
|
|
138
|
-
config.workersCount = 2
|
|
149
|
+
config.workersCount = 2;
|
|
139
150
|
}
|
|
140
151
|
|
|
141
|
-
reactOnRailsProNodeRenderer(config)
|
|
152
|
+
reactOnRailsProNodeRenderer(config);
|
|
142
153
|
```
|
|
143
154
|
|
|
144
155
|
## Instructions for using a branch
|
|
145
156
|
|
|
146
157
|
Install the node-renderer executable, possibly globally. Substitute the branch name or tag for `master`
|
|
158
|
+
|
|
147
159
|
```
|
|
148
160
|
yarn global add https://<your-github-token>:x-oauth-basic@github.com/shakacode/react_on_rails_pro.git\#master
|
|
149
161
|
```
|
|
@@ -156,28 +168,34 @@ Login into npm
|
|
|
156
168
|
|
|
157
169
|
```bash
|
|
158
170
|
npm install @shakacode-tools/react-on-rails-pro-node-renderer@<version>
|
|
159
|
-
```
|
|
171
|
+
```
|
|
160
172
|
|
|
161
173
|
or edit package.json directly
|
|
174
|
+
|
|
162
175
|
```json
|
|
163
176
|
"@shakacode-tools/react-on-rails-pro-node-renderer": "<version>"
|
|
164
|
-
```
|
|
177
|
+
```
|
|
165
178
|
|
|
166
179
|
### Configuration
|
|
180
|
+
|
|
167
181
|
See [NodeRenderer JavaScript Configuration](./node-renderer/js-configuration.md).
|
|
168
182
|
|
|
169
183
|
#### Webpack Configuration
|
|
184
|
+
|
|
170
185
|
Set your server bundle webpack configuration to use a target of `node` per the [Webpack docs](https://webpack.js.org/concepts/targets/#usage).
|
|
171
186
|
|
|
172
187
|
## Authentication when using Github packages
|
|
188
|
+
|
|
173
189
|
[Auth for the npm package](https://docs.github.com/en/packages/using-github-packages-with-your-projects-ecosystem/configuring-npm-for-use-with-github-packages#authenticating-to-github-packages)
|
|
174
190
|
|
|
175
191
|
Create a new ~/.npmrc file if one doesn't exist.
|
|
192
|
+
|
|
176
193
|
```
|
|
177
194
|
//npm.pkg.github.com/:_authToken=TOKEN
|
|
178
|
-
```
|
|
195
|
+
```
|
|
179
196
|
|
|
180
197
|
To configure bundler if you don't want the token in your Gemfile:
|
|
198
|
+
|
|
181
199
|
```
|
|
182
200
|
bundle config https://rubygems.pkg.github.com/OWNER USERNAME:TOKEN
|
|
183
|
-
```
|
|
201
|
+
```
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
# Requirements
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
- You must use React on Rails v11.0.7 or higher.
|
|
3
4
|
|
|
4
5
|
# Install the Gem and the Node Module
|
|
6
|
+
|
|
5
7
|
See [Installation](../installation.md).
|
|
6
8
|
|
|
7
9
|
# Setup Node Renderer Server
|
|
10
|
+
|
|
8
11
|
**node-renderer** is a standalone Node application to serve React SSR requests from a **Rails** client. You don't need any **Ruby** code to setup and launch it. You can configure with the command line or with a launch file.
|
|
9
12
|
|
|
10
13
|
## Simple Command Line for node-renderer
|
|
11
14
|
|
|
12
15
|
1. ENV values for the default config are (See [JS Configuration](./js-configuration.md) for more details):
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
- `RENDERER_PORT`
|
|
17
|
+
- `RENDERER_LOG_LEVEL`
|
|
18
|
+
- `RENDERER_BUNDLE_PATH`
|
|
19
|
+
- `RENDERER_WORKERS_COUNT`
|
|
20
|
+
- `RENDERER_PASSWORD`
|
|
21
|
+
- `RENDERER_ALL_WORKERS_RESTART_INTERVAL`
|
|
22
|
+
- `RENDERER_DELAY_BETWEEN_INDIVIDUAL_WORKER_RESTARTS`
|
|
23
|
+
- `RENDERER_SUPPORT_MODULES`
|
|
21
24
|
2. Configure ENV values and run the command. Note, you can set port with args `-p <PORT>`. For example, assuming node-renderer is in your path:
|
|
22
25
|
```
|
|
23
26
|
RENDERER_BUNDLE_PATH=/app/.node-renderer-bundles node-renderer
|
|
@@ -25,6 +28,7 @@ See [Installation](../installation.md).
|
|
|
25
28
|
3. You can use a command line argument of `-p SOME_PORT` to override any ENV value for the PORT.
|
|
26
29
|
|
|
27
30
|
## JavaScript Configuration File
|
|
31
|
+
|
|
28
32
|
For the most control over the setup, create a JavaScript file to start the NodeRenderer.
|
|
29
33
|
|
|
30
34
|
1. Create some project directory, let's say `renderer-app`:
|
|
@@ -38,22 +42,24 @@ For the most control over the setup, create a JavaScript file to start the NodeR
|
|
|
38
42
|
yarn init
|
|
39
43
|
yarn add https://[your-github-token]:x-oauth-basic@github.com/shakacode/react_on_rails_pro.git\#master
|
|
40
44
|
```
|
|
41
|
-
4. Configure a JavaScript file that will launch the rendering server per the docs in [Node Renderer JavaScript Configuration](./js-configuration.md). For example, create a file `node-renderer.js`. Here is a simple example that uses all the defaults except for
|
|
45
|
+
4. Configure a JavaScript file that will launch the rendering server per the docs in [Node Renderer JavaScript Configuration](./js-configuration.md). For example, create a file `node-renderer.js`. Here is a simple example that uses all the defaults except for serverBundleCachePath:
|
|
42
46
|
|
|
43
47
|
```javascript
|
|
44
48
|
import path from 'path';
|
|
45
49
|
import reactOnRailsProNodeRenderer from '@shakacode-tools/react-on-rails-pro-node-renderer';
|
|
46
50
|
|
|
47
51
|
const config = {
|
|
48
|
-
|
|
52
|
+
serverBundleCachePath: path.resolve(__dirname, '../.node-renderer-bundles'),
|
|
49
53
|
};
|
|
50
54
|
|
|
51
55
|
reactOnRailsProNodeRenderer(config);
|
|
52
56
|
```
|
|
57
|
+
|
|
53
58
|
5. Now you can launch your renderer server with `node node-renderer.js`. You will probably add a script to your `package.json`.
|
|
54
59
|
6. You can use a command line argument of `-p SOME_PORT` to override any configured or ENV value for the port.
|
|
55
60
|
|
|
56
61
|
# Setup Rails Application
|
|
62
|
+
|
|
57
63
|
Create `config/initializers/react_on_rails_pro.rb` and configure the **renderer server**. See configuration values in [Configuration](../configuration.md). Pay attention to:
|
|
58
64
|
|
|
59
65
|
1. Set `config.server_renderer = "NodeRenderer"`
|
|
@@ -61,25 +67,26 @@ Create `config/initializers/react_on_rails_pro.rb` and configure the **renderer
|
|
|
61
67
|
3. Configure values beginning with `renderer_`
|
|
62
68
|
4. Use ENV values for values like `renderer_url` so that your deployed server is properly configured. If the ENV value is unset, the default for the renderer_url is `localhost:3800`.
|
|
63
69
|
5. Here's a tiny example using mostly defaults:
|
|
70
|
+
|
|
64
71
|
```ruby
|
|
65
72
|
ReactOnRailsPro.configure do |config|
|
|
66
73
|
config.server_renderer = "NodeRenderer"
|
|
67
|
-
|
|
68
|
-
# when this ENV value is not defined, the local server at localhost:3800 is used
|
|
69
|
-
config.renderer_url = ENV["REACT_RENDERER_URL"]
|
|
74
|
+
|
|
75
|
+
# when this ENV value is not defined, the local server at localhost:3800 is used
|
|
76
|
+
config.renderer_url = ENV["REACT_RENDERER_URL"]
|
|
70
77
|
end
|
|
71
78
|
```
|
|
72
79
|
|
|
73
80
|
## Troublshooting
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
- See [JS Memory Leaks](../js-memory-leaks.md).
|
|
83
|
+
|
|
77
84
|
## Upgrading
|
|
78
85
|
|
|
79
86
|
The NodeRenderer has a protocol version on both the Rails and Node sides. If the Rails server sends a protocol version that does not match the Node side, an error is returned. Ideally, you want to keep both the Rails and Node sides at the same version.
|
|
80
87
|
|
|
81
88
|
## References
|
|
82
89
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
- [Installation](../installation.md).
|
|
91
|
+
- [Rails Options for node-renderer](../configuration.md)
|
|
92
|
+
- [JS Options for node-renderer](./js-configuration.md)
|
|
@@ -8,39 +8,41 @@ The values in this file must be kept in sync with with the `config/initializers/
|
|
|
8
8
|
|
|
9
9
|
Here are the options available for the JavaScript renderer configuration object, as well as the available default ENV values if using the command line program node-renderer.
|
|
10
10
|
|
|
11
|
-
[//]: #
|
|
11
|
+
[//]: # 'If you change text here, you may want to update comments in packages/node-renderer/src/shared/configBuilder.ts as well.'
|
|
12
12
|
|
|
13
13
|
1. **port** (default: `process.env.RENDERER_PORT || 3800`) - The port the renderer should listen to.
|
|
14
|
-
[On Heroku](https://devcenter.heroku.com/articles/dyno-startup-behavior#port-binding-of-web-dynos) or [ControlPlane](https://docs.controlplane.com/reference/workload/containers#port-variable) you may want to use `process.env.PORT`.
|
|
14
|
+
[On Heroku](https://devcenter.heroku.com/articles/dyno-startup-behavior#port-binding-of-web-dynos) or [ControlPlane](https://docs.controlplane.com/reference/workload/containers#port-variable) you may want to use `process.env.PORT`.
|
|
15
15
|
1. **logLevel** (default: `process.env.RENDERER_LOG_LEVEL || 'info'`) - The renderer log level. Set it to `silent` to turn logging off.
|
|
16
|
-
[Available levels](https://getpino.io/#/docs/api?id=levels): `{ fatal: 60, error: 50, warn: 40, info: 30, debug: 20, trace: 10 }`. `silent` can be used as well.
|
|
16
|
+
[Available levels](https://getpino.io/#/docs/api?id=levels): `{ fatal: 60, error: 50, warn: 40, info: 30, debug: 20, trace: 10 }`. `silent` can be used as well.
|
|
17
17
|
1. **logHttpLevel** (default: `process.env.RENDERER_LOG_HTTP_LEVEL || 'error'`) - The HTTP server log level (same allowed values as `logLevel`).
|
|
18
18
|
1. **fastifyServerOptions** (default: `{}`) - Additional options to pass to the Fastify server factory. See [Fastify documentation](https://fastify.dev/docs/latest/Reference/Server/#factory).
|
|
19
|
-
1. **
|
|
19
|
+
1. **serverBundleCachePath** (default: `process.env.RENDERER_SERVER_BUNDLE_CACHE_PATH || process.env.RENDERER_BUNDLE_PATH || '/tmp/react-on-rails-pro-node-renderer-bundles'` ) - Path to a cache directory where uploaded server bundle files will be stored. This is distinct from Shakapacker's public asset directory. For example you can set it to `path.resolve(__dirname, './.node-renderer-bundles')` if you configured renderer from the `/` directory of your app.
|
|
20
20
|
1. **workersCount** (default: `process.env.RENDERER_WORKERS_COUNT || defaultWorkersCount()` where default is your CPUs count - 1) - Number of workers that will be forked to serve rendering requests. If you set this manually make sure that value is a **Number** and is `>= 0`. Setting this to `0` will run the renderer in a single process mode without forking any workers, which is useful for debugging purposes. For production use, the value should be `>= 1`.
|
|
21
21
|
1. **password** (default: `env.RENDERER_PASSWORD`) - The password expected to receive from the **Rails client** to authenticate rendering requests.
|
|
22
|
-
If no password is set, no authentication will be required.
|
|
22
|
+
If no password is set, no authentication will be required.
|
|
23
23
|
1. **allWorkersRestartInterval** (default: `env.RENDERER_ALL_WORKERS_RESTART_INTERVAL`) - Interval in minutes between scheduled restarts of all workers. By default restarts are not enabled. If restarts are enabled, `delayBetweenIndividualWorkerRestarts` should also be set.
|
|
24
24
|
1. **delayBetweenIndividualWorkerRestarts** (default: `env.RENDERER_DELAY_BETWEEN_INDIVIDUAL_WORKER_RESTARTS`) - Interval in minutes between individual worker restarts (when cluster restart is triggered). By default restarts are not enabled. If restarts are enabled, `allWorkersRestartInterval` should also be set.
|
|
25
|
+
1. **gracefulWorkerRestartTimeout**: (default: `env.GRACEFUL_WORKER_RESTART_TIMEOUT`) - Time in seconds that the master waits for a worker to gracefully restart (after serving all active requests) before killing it. Use this when you want to avoid situations where a worker gets stuck in an infinite loop and never restarts. This config is only usable if worker restart is enabled. The timeout starts when the worker should restart; if it elapses without a restart, the worker is killed.
|
|
25
26
|
1. **maxDebugSnippetLength** (default: 1000) - If the rendering request is longer than this, it will be truncated in exception and logging messages.
|
|
26
|
-
1. **supportModules** - (default: `env.RENDERER_SUPPORT_MODULES || null`) - If set to true, `supportModules` enables the server-bundle code to call a default set of NodeJS global objects and functions that get added to the VM context:
|
|
27
|
-
`{ Buffer, TextDecoder, TextEncoder, URLSearchParams, ReadableStream, process, setTimeout, setInterval, setImmediate, clearTimeout, clearInterval, clearImmediate, queueMicrotask }`.
|
|
28
|
-
This option is required to equal `true` if you want to use loadable components.
|
|
29
|
-
Setting this value to false causes the NodeRenderer to behave like ExecJS.
|
|
30
|
-
See also `stubTimers`.
|
|
31
|
-
1. **additionalContext** - (default: `null`) - additionalContext enables you to specify additional NodeJS objects (usually from https://nodejs.org/api/globals.html) to add to the VM context in addition to our `supportModules` defaults.
|
|
32
|
-
Object shorthand notation may be used, but is not required.
|
|
33
|
-
Example: `{ URL, Crypto }`
|
|
34
|
-
1. **stubTimers** - (default: `env.RENDERER_STUB_TIMERS` if that environment variable is set, `true` otherwise) - With this option set, use of functions `setTimeout`, `setInterval`, `setImmediate`, `clearTimeout`, `clearInterval`, `clearImmediate`, and `queueMicrotask` will do nothing during server-rendering.
|
|
35
|
-
This is useful when using dependencies like [react-virtuoso](https://github.com/petyosi/react-virtuoso) that use these functions during hydration.
|
|
36
|
-
In RORP, hydration typically is synchronous and single-task (unless you use streaming) and thus callbacks passed to
|
|
37
|
-
Because these functions are valid client-side, they are ignored on server-side rendering without errors or warnings.
|
|
38
|
-
See also `supportModules`.
|
|
27
|
+
1. **supportModules** - (default: `env.RENDERER_SUPPORT_MODULES || null`) - If set to true, `supportModules` enables the server-bundle code to call a default set of NodeJS global objects and functions that get added to the VM context:
|
|
28
|
+
`{ Buffer, TextDecoder, TextEncoder, URLSearchParams, ReadableStream, process, setTimeout, setInterval, setImmediate, clearTimeout, clearInterval, clearImmediate, queueMicrotask }`.
|
|
29
|
+
This option is required to equal `true` if you want to use loadable components.
|
|
30
|
+
Setting this value to false causes the NodeRenderer to behave like ExecJS.
|
|
31
|
+
See also `stubTimers`.
|
|
32
|
+
1. **additionalContext** - (default: `null`) - additionalContext enables you to specify additional NodeJS objects (usually from https://nodejs.org/api/globals.html) to add to the VM context in addition to our `supportModules` defaults.
|
|
33
|
+
Object shorthand notation may be used, but is not required.
|
|
34
|
+
Example: `{ URL, Crypto }`
|
|
35
|
+
1. **stubTimers** - (default: `env.RENDERER_STUB_TIMERS` if that environment variable is set, `true` otherwise) - With this option set, use of functions `setTimeout`, `setInterval`, `setImmediate`, `clearTimeout`, `clearInterval`, `clearImmediate`, and `queueMicrotask` will do nothing during server-rendering.
|
|
36
|
+
This is useful when using dependencies like [react-virtuoso](https://github.com/petyosi/react-virtuoso) that use these functions during hydration.
|
|
37
|
+
In RORP, hydration typically is synchronous and single-task (unless you use streaming) and thus callbacks passed to task-scheduling functions should never run during server-side rendering.
|
|
38
|
+
Because these functions are valid client-side, they are ignored on server-side rendering without errors or warnings.
|
|
39
|
+
See also `supportModules`.
|
|
39
40
|
|
|
40
41
|
Deprecated options:
|
|
41
42
|
|
|
42
|
-
1. **
|
|
43
|
-
|
|
43
|
+
1. **bundlePath** - Renamed to `serverBundleCachePath`. The old name will continue to work but will log a deprecation warning.
|
|
44
|
+
1. **honeybadgerApiKey**, **sentryDsn**, **sentryTracing**, **sentryTracesSampleRate** - Deprecated and have no effect.
|
|
45
|
+
If you have any of them set, see [Error Reporting and Tracing](./error-reporting-and-tracing.md) for the new way to set up error reporting and tracing.
|
|
44
46
|
1. **includeTimerPolyfills** - Renamed to `stubTimers`.
|
|
45
47
|
|
|
46
48
|
## Example Launch Files
|
|
@@ -52,13 +54,14 @@ If you have any of them set, see [Error Reporting and Tracing](./error-reporting
|
|
|
52
54
|
### Simple example:
|
|
53
55
|
|
|
54
56
|
Create a file './node-renderer.js'
|
|
57
|
+
|
|
55
58
|
```js
|
|
56
59
|
import path from 'path';
|
|
57
60
|
import { reactOnRailsProNodeRenderer } from '@shakacode-tools/react-on-rails-pro-node-renderer';
|
|
58
61
|
|
|
59
62
|
const config = {
|
|
60
63
|
// Save bundles to relative "./.node-renderer-bundles" dir of our app
|
|
61
|
-
|
|
64
|
+
serverBundleCachePath: path.resolve(__dirname, './.node-renderer-bundles'),
|
|
62
65
|
|
|
63
66
|
// All other values are the defaults, as described above
|
|
64
67
|
};
|
|
@@ -75,7 +78,6 @@ else if (process.env.CI) {
|
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
reactOnRailsProNodeRenderer(config);
|
|
78
|
-
|
|
79
81
|
```
|
|
80
82
|
|
|
81
83
|
And add this line to your `scripts` section of `package.json`
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
# Node Renderer Troubleshooting
|
|
2
2
|
|
|
3
3
|
- If you enabled restarts (having `allWorkersRestartInterval` and `delayBetweenIndividualWorkerRestarts`), you should set it with a high number to avoid the app from crashing because all Node renderer workers are stopped/killed.
|
|
4
|
+
|
|
5
|
+
- If your app contains streamed pages that take too much time to be streamed to the client, ensure to not set the `gracefulWorkerRestartTimeout` parameter or set to a high number, so the worker is not killed while it's still serving an active request.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import cluster from 'cluster';
|
|
7
7
|
import log from '../shared/log';
|
|
8
|
+
import { SHUTDOWN_WORKER_MESSAGE } from '../shared/utils';
|
|
8
9
|
|
|
9
10
|
const MILLISECONDS_IN_MINUTE = 60000;
|
|
10
11
|
|
|
@@ -14,26 +15,47 @@ declare module 'cluster' {
|
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
export = function restartWorkers(
|
|
18
|
+
export = async function restartWorkers(
|
|
19
|
+
delayBetweenIndividualWorkerRestarts: number,
|
|
20
|
+
gracefulWorkerRestartTimeout: number | undefined,
|
|
21
|
+
) {
|
|
18
22
|
log.info('Started scheduled restart of workers');
|
|
19
23
|
|
|
20
|
-
let delay = 0;
|
|
21
24
|
if (!cluster.workers) {
|
|
22
25
|
throw new Error('No workers to restart');
|
|
23
26
|
}
|
|
24
|
-
Object.values(cluster.workers).
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
for (const worker of Object.values(cluster.workers).filter((w) => !!w)) {
|
|
28
|
+
log.debug('Kill worker #%d', worker.id);
|
|
29
|
+
worker.isScheduledRestart = true;
|
|
30
|
+
|
|
31
|
+
worker.send(SHUTDOWN_WORKER_MESSAGE);
|
|
32
|
+
|
|
33
|
+
// It's inteded to restart worker in sequence, it shouldn't happens in parallel
|
|
34
|
+
// eslint-disable-next-line no-await-in-loop
|
|
35
|
+
await new Promise<void>((resolve) => {
|
|
36
|
+
let timeout: NodeJS.Timeout;
|
|
37
|
+
|
|
38
|
+
const onExit = () => {
|
|
39
|
+
clearTimeout(timeout);
|
|
40
|
+
resolve();
|
|
41
|
+
};
|
|
42
|
+
worker.on('exit', onExit);
|
|
43
|
+
|
|
44
|
+
// Zero means no timeout
|
|
45
|
+
if (gracefulWorkerRestartTimeout) {
|
|
46
|
+
timeout = setTimeout(() => {
|
|
47
|
+
log.debug('Worker #%d timed out, forcing kill it', worker.id);
|
|
48
|
+
worker.destroy();
|
|
49
|
+
worker.off('exit', onExit);
|
|
50
|
+
resolve();
|
|
51
|
+
}, gracefulWorkerRestartTimeout);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
// eslint-disable-next-line no-await-in-loop
|
|
55
|
+
await new Promise((resolve) => {
|
|
56
|
+
setTimeout(resolve, delayBetweenIndividualWorkerRestarts * MILLISECONDS_IN_MINUTE);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
log.info('Finished scheduled restart of workers');
|
|
39
61
|
};
|
|
@@ -19,7 +19,12 @@ export = function masterRun(runningConfig?: Partial<Config>) {
|
|
|
19
19
|
|
|
20
20
|
// Store config in app state. From now it can be loaded by any module using getConfig():
|
|
21
21
|
const config = buildConfig(runningConfig);
|
|
22
|
-
const {
|
|
22
|
+
const {
|
|
23
|
+
workersCount,
|
|
24
|
+
allWorkersRestartInterval,
|
|
25
|
+
delayBetweenIndividualWorkerRestarts,
|
|
26
|
+
gracefulWorkerRestartTimeout,
|
|
27
|
+
} = config;
|
|
23
28
|
|
|
24
29
|
logSanitizedConfig();
|
|
25
30
|
|
|
@@ -48,9 +53,15 @@ export = function masterRun(runningConfig?: Partial<Config>) {
|
|
|
48
53
|
allWorkersRestartInterval,
|
|
49
54
|
delayBetweenIndividualWorkerRestarts,
|
|
50
55
|
);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
|
|
57
|
+
const allWorkersRestartIntervalMS = allWorkersRestartInterval * MILLISECONDS_IN_MINUTE;
|
|
58
|
+
const scheduleWorkersRestart = () => {
|
|
59
|
+
void restartWorkers(delayBetweenIndividualWorkerRestarts, gracefulWorkerRestartTimeout).finally(() => {
|
|
60
|
+
setTimeout(scheduleWorkersRestart, allWorkersRestartIntervalMS);
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
setTimeout(scheduleWorkersRestart, allWorkersRestartIntervalMS);
|
|
54
65
|
} else if (allWorkersRestartInterval || delayBetweenIndividualWorkerRestarts) {
|
|
55
66
|
log.error(
|
|
56
67
|
"Misconfiguration, please provide both 'allWorkersRestartInterval' and " +
|
|
@@ -34,8 +34,11 @@ export interface Config {
|
|
|
34
34
|
// Additional options to pass to the Fastify server factory.
|
|
35
35
|
// See https://fastify.dev/docs/latest/Reference/Server/#factory.
|
|
36
36
|
fastifyServerOptions: FastifyServerOptions<http2.Http2Server>;
|
|
37
|
-
// Path to a
|
|
38
|
-
|
|
37
|
+
// Path to a cache directory where uploaded server bundle files will be stored.
|
|
38
|
+
// This is distinct from Shakapacker's public asset directory.
|
|
39
|
+
serverBundleCachePath: string;
|
|
40
|
+
// @deprecated Use serverBundleCachePath instead. This will be removed in a future version.
|
|
41
|
+
bundlePath?: string;
|
|
39
42
|
// If set to true, `supportModules` enables the server-bundle code to call a default set of NodeJS
|
|
40
43
|
// global objects and functions that get added to the VM context:
|
|
41
44
|
// `{ Buffer, TextDecoder, TextEncoder, URLSearchParams, ReadableStream, process, setTimeout, setInterval, setImmediate, clearTimeout, clearInterval, clearImmediate, queueMicrotask }`.
|
|
@@ -58,6 +61,9 @@ export interface Config {
|
|
|
58
61
|
allWorkersRestartInterval: number | undefined;
|
|
59
62
|
// Time in minutes between each worker restarting when restarting all workers
|
|
60
63
|
delayBetweenIndividualWorkerRestarts: number | undefined;
|
|
64
|
+
// Time in seconds to wait for worker to restart before killing it
|
|
65
|
+
// Set it to 0 or undefined to never kill the worker
|
|
66
|
+
gracefulWorkerRestartTimeout: number | undefined;
|
|
61
67
|
// If the rendering request is longer than this, it will be truncated in exception and logging messages
|
|
62
68
|
maxDebugSnippetLength: number;
|
|
63
69
|
// @deprecated See https://www.shakacode.com/react-on-rails-pro/docs/node-renderer/error-reporting-and-tracing.
|
|
@@ -99,7 +105,7 @@ function defaultWorkersCount() {
|
|
|
99
105
|
}
|
|
100
106
|
|
|
101
107
|
// Find the .node-renderer-bundles folder if it exists, otherwise use /tmp
|
|
102
|
-
function
|
|
108
|
+
function defaultServerBundleCachePath() {
|
|
103
109
|
let currentDir = process.cwd();
|
|
104
110
|
const maxDepth = 10;
|
|
105
111
|
for (let i = 0; i < maxDepth; i += 1) {
|
|
@@ -145,7 +151,8 @@ const defaultConfig: Config = {
|
|
|
145
151
|
|
|
146
152
|
fastifyServerOptions: {},
|
|
147
153
|
|
|
148
|
-
|
|
154
|
+
serverBundleCachePath:
|
|
155
|
+
env.RENDERER_SERVER_BUNDLE_CACHE_PATH || env.RENDERER_BUNDLE_PATH || defaultServerBundleCachePath(),
|
|
149
156
|
|
|
150
157
|
supportModules: truthy(env.RENDERER_SUPPORT_MODULES),
|
|
151
158
|
|
|
@@ -165,6 +172,10 @@ const defaultConfig: Config = {
|
|
|
165
172
|
? parseInt(env.RENDERER_DELAY_BETWEEN_INDIVIDUAL_WORKER_RESTARTS, 10)
|
|
166
173
|
: undefined,
|
|
167
174
|
|
|
175
|
+
gracefulWorkerRestartTimeout: env.GRACEFUL_WORKER_RESTART_TIMEOUT
|
|
176
|
+
? parseInt(env.GRACEFUL_WORKER_RESTART_TIMEOUT, 10)
|
|
177
|
+
: undefined,
|
|
178
|
+
|
|
168
179
|
maxDebugSnippetLength: MAX_DEBUG_SNIPPET_LENGTH,
|
|
169
180
|
|
|
170
181
|
// default to true if empty, otherwise it is set to false
|
|
@@ -185,7 +196,10 @@ function envValuesUsed() {
|
|
|
185
196
|
RENDERER_PORT: !userConfig.port && env.RENDERER_PORT,
|
|
186
197
|
RENDERER_LOG_LEVEL: !userConfig.logLevel && env.RENDERER_LOG_LEVEL,
|
|
187
198
|
RENDERER_LOG_HTTP_LEVEL: !userConfig.logHttpLevel && env.RENDERER_LOG_HTTP_LEVEL,
|
|
188
|
-
|
|
199
|
+
RENDERER_SERVER_BUNDLE_CACHE_PATH:
|
|
200
|
+
!userConfig.serverBundleCachePath && env.RENDERER_SERVER_BUNDLE_CACHE_PATH,
|
|
201
|
+
RENDERER_BUNDLE_PATH:
|
|
202
|
+
!userConfig.serverBundleCachePath && !userConfig.bundlePath && env.RENDERER_BUNDLE_PATH,
|
|
189
203
|
RENDERER_WORKERS_COUNT: !userConfig.workersCount && env.RENDERER_WORKERS_COUNT,
|
|
190
204
|
RENDERER_PASSWORD: !userConfig.password && env.RENDERER_PASSWORD && '<MASKED>',
|
|
191
205
|
RENDERER_SUPPORT_MODULES: !('supportModules' in userConfig) && env.RENDERER_SUPPORT_MODULES,
|
|
@@ -195,6 +209,8 @@ function envValuesUsed() {
|
|
|
195
209
|
RENDERER_DELAY_BETWEEN_INDIVIDUAL_WORKER_RESTARTS:
|
|
196
210
|
!userConfig.delayBetweenIndividualWorkerRestarts &&
|
|
197
211
|
env.RENDERER_DELAY_BETWEEN_INDIVIDUAL_WORKER_RESTARTS,
|
|
212
|
+
GRACEFUL_WORKER_RESTART_TIMEOUT:
|
|
213
|
+
!userConfig.gracefulWorkerRestartTimeout && env.GRACEFUL_WORKER_RESTART_TIMEOUT,
|
|
198
214
|
INCLUDE_TIMER_POLYFILLS: !('includeTimerPolyfills' in userConfig) && env.INCLUDE_TIMER_POLYFILLS,
|
|
199
215
|
REPLAY_SERVER_ASYNC_OPERATION_LOGS:
|
|
200
216
|
!userConfig.replayServerAsyncOperationLogs && env.REPLAY_SERVER_ASYNC_OPERATION_LOGS,
|
|
@@ -209,6 +225,7 @@ function sanitizedSettings(aConfig: Partial<Config> | undefined, defaultValue?:
|
|
|
209
225
|
password: aConfig.password != null ? '<MASKED>' : defaultValue,
|
|
210
226
|
allWorkersRestartInterval: aConfig.allWorkersRestartInterval || defaultValue,
|
|
211
227
|
delayBetweenIndividualWorkerRestarts: aConfig.delayBetweenIndividualWorkerRestarts || defaultValue,
|
|
228
|
+
gracefulWorkerRestartTimeout: aConfig.gracefulWorkerRestartTimeout || defaultValue,
|
|
212
229
|
}
|
|
213
230
|
: {};
|
|
214
231
|
}
|
|
@@ -231,6 +248,28 @@ export function buildConfig(providedUserConfig?: Partial<Config>): Config {
|
|
|
231
248
|
userConfig = providedUserConfig || {};
|
|
232
249
|
config = { ...defaultConfig, ...userConfig };
|
|
233
250
|
|
|
251
|
+
// Handle bundlePath deprecation
|
|
252
|
+
if ('bundlePath' in userConfig) {
|
|
253
|
+
log.warn(
|
|
254
|
+
'bundlePath is deprecated and will be removed in a future version. ' +
|
|
255
|
+
'Use serverBundleCachePath instead. This path stores uploaded server bundles for the node renderer, ' +
|
|
256
|
+
'not client-side webpack assets from Shakapacker.',
|
|
257
|
+
);
|
|
258
|
+
// If serverBundleCachePath is not set, use bundlePath as fallback
|
|
259
|
+
if (
|
|
260
|
+
userConfig.bundlePath &&
|
|
261
|
+
(!config.serverBundleCachePath || config.serverBundleCachePath === defaultConfig.serverBundleCachePath)
|
|
262
|
+
) {
|
|
263
|
+
config.serverBundleCachePath = userConfig.bundlePath;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (env.RENDERER_BUNDLE_PATH && !env.RENDERER_SERVER_BUNDLE_CACHE_PATH) {
|
|
267
|
+
log.warn(
|
|
268
|
+
'RENDERER_BUNDLE_PATH environment variable is deprecated and will be removed in a future version. ' +
|
|
269
|
+
'Use RENDERER_SERVER_BUNDLE_CACHE_PATH instead.',
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
234
273
|
config.supportModules = truthy(config.supportModules);
|
|
235
274
|
|
|
236
275
|
if (config.maxVMPoolSize <= 0 || !Number.isInteger(config.maxVMPoolSize)) {
|
|
@@ -11,6 +11,8 @@ import type { RenderResult } from '../worker/vm';
|
|
|
11
11
|
|
|
12
12
|
export const TRUNCATION_FILLER = '\n... TRUNCATED ...\n';
|
|
13
13
|
|
|
14
|
+
export const SHUTDOWN_WORKER_MESSAGE = 'NODE_RENDERER_SHUTDOWN_WORKER';
|
|
15
|
+
|
|
14
16
|
export function workerIdLabel() {
|
|
15
17
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- worker is nullable in the primary process
|
|
16
18
|
return cluster?.worker?.id || 'NO WORKER ID';
|
|
@@ -155,8 +157,8 @@ export const delay = (milliseconds: number) =>
|
|
|
155
157
|
});
|
|
156
158
|
|
|
157
159
|
export function getBundleDirectory(bundleTimestamp: string | number) {
|
|
158
|
-
const {
|
|
159
|
-
return path.join(
|
|
160
|
+
const { serverBundleCachePath } = getConfig();
|
|
161
|
+
return path.join(serverBundleCachePath, `${bundleTimestamp}`);
|
|
160
162
|
}
|
|
161
163
|
|
|
162
164
|
export function getRequestBundleFilePath(bundleTimestamp: string | number) {
|