react_on_rails 16.2.0.beta.4 → 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 +27 -8
- data/CONTRIBUTING.md +1 -1
- data/Gemfile.development_dependencies +0 -1
- data/Gemfile.lock +1 -9
- data/bin/ci-rerun-failures +39 -16
- data/bin/ci-run-failed-specs +1 -1
- data/bin/ci-switch-config +8 -2
- data/bin/lefthook/ruby-autofix +2 -1
- 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/dev/server_manager.rb +11 -4
- data/lib/react_on_rails/doctor.rb +245 -0
- data/lib/react_on_rails/helper.rb +9 -0
- data/lib/react_on_rails/version.rb +1 -1
- data/react_on_rails_pro/CHANGELOG.md +7 -0
- data/react_on_rails_pro/CONTRIBUTING.md +2 -13
- data/react_on_rails_pro/Gemfile.lock +21 -3
- 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 +106 -104
- data/react_on_rails_pro/docs/node-renderer/basics.md +3 -3
- 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 +1 -1
- 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/rakelib/public_key_management.rake +6 -5
- data/react_on_rails_pro/react_on_rails_pro.gemspec +1 -0
- data/react_on_rails_pro/spec/dummy/Gemfile.lock +20 -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 +3 -3
- 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 +20 -14
- 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
- metadata +2 -3
- data/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/TestingStreamableComponent.jsx +0 -15
|
@@ -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); });
|
|
@@ -13,9 +13,9 @@ require "uri"
|
|
|
13
13
|
# rake react_on_rails_pro:verify_public_key # Verify current configuration
|
|
14
14
|
# rake react_on_rails_pro:public_key_help # Show help
|
|
15
15
|
|
|
16
|
-
namespace :react_on_rails_pro do
|
|
16
|
+
namespace :react_on_rails_pro do # rubocop:disable Metrics/BlockLength
|
|
17
17
|
desc "Update the public key for React on Rails Pro license validation"
|
|
18
|
-
task :update_public_key, [:source] do |_task, args|
|
|
18
|
+
task :update_public_key, [:source] do |_task, args| # rubocop:disable Metrics/BlockLength
|
|
19
19
|
source = args[:source] || "production"
|
|
20
20
|
|
|
21
21
|
# Determine the API URL based on the source
|
|
@@ -68,7 +68,7 @@ namespace :react_on_rails_pro do
|
|
|
68
68
|
# ShakaCode's public key for React on Rails Pro license verification
|
|
69
69
|
# The private key corresponding to this public key is held by ShakaCode
|
|
70
70
|
# and is never committed to the repository
|
|
71
|
-
# Last updated: #{Time.now.utc.strftime(
|
|
71
|
+
# Last updated: #{Time.now.utc.strftime('%Y-%m-%d %H:%M:%S UTC')}
|
|
72
72
|
# Source: #{api_url}
|
|
73
73
|
#
|
|
74
74
|
# You can update this public key by running the rake task:
|
|
@@ -86,12 +86,13 @@ namespace :react_on_rails_pro do
|
|
|
86
86
|
puts "✅ Updated Ruby public key: #{ruby_file_path}"
|
|
87
87
|
|
|
88
88
|
# Update Node/TypeScript public key file
|
|
89
|
-
node_file_path = File.join(File.dirname(__FILE__), "..", "packages", "node-renderer", "src", "shared",
|
|
89
|
+
node_file_path = File.join(File.dirname(__FILE__), "..", "packages", "node-renderer", "src", "shared",
|
|
90
|
+
"licensePublicKey.ts")
|
|
90
91
|
node_content = <<~TYPESCRIPT
|
|
91
92
|
// ShakaCode's public key for React on Rails Pro license verification
|
|
92
93
|
// The private key corresponding to this public key is held by ShakaCode
|
|
93
94
|
// and is never committed to the repository
|
|
94
|
-
// Last updated: #{Time.now.utc.strftime(
|
|
95
|
+
// Last updated: #{Time.now.utc.strftime('%Y-%m-%d %H:%M:%S UTC')}
|
|
95
96
|
// Source: #{api_url}
|
|
96
97
|
//
|
|
97
98
|
// You can update this public key by running the rake task:
|
|
@@ -37,6 +37,7 @@ Gem::Specification.new do |s|
|
|
|
37
37
|
s.add_runtime_dependency "execjs", "~> 2.9"
|
|
38
38
|
s.add_runtime_dependency "httpx", "~> 1.5"
|
|
39
39
|
s.add_runtime_dependency "jwt", "~> 2.7"
|
|
40
|
+
s.add_runtime_dependency "async", ">= 2.6"
|
|
40
41
|
s.add_runtime_dependency "rainbow"
|
|
41
42
|
s.add_runtime_dependency "react_on_rails", ReactOnRails::VERSION
|
|
42
43
|
s.add_development_dependency "bundler"
|
|
@@ -9,7 +9,7 @@ GIT
|
|
|
9
9
|
PATH
|
|
10
10
|
remote: ../../..
|
|
11
11
|
specs:
|
|
12
|
-
react_on_rails (16.2.0.beta.
|
|
12
|
+
react_on_rails (16.2.0.beta.8)
|
|
13
13
|
addressable
|
|
14
14
|
connection_pool
|
|
15
15
|
execjs (~> 2.5)
|
|
@@ -20,14 +20,15 @@ PATH
|
|
|
20
20
|
PATH
|
|
21
21
|
remote: ../..
|
|
22
22
|
specs:
|
|
23
|
-
react_on_rails_pro (16.2.0.beta.
|
|
23
|
+
react_on_rails_pro (16.2.0.beta.8)
|
|
24
24
|
addressable
|
|
25
|
+
async (>= 2.6)
|
|
25
26
|
connection_pool
|
|
26
27
|
execjs (~> 2.9)
|
|
27
28
|
httpx (~> 1.5)
|
|
28
29
|
jwt (~> 2.7)
|
|
29
30
|
rainbow
|
|
30
|
-
react_on_rails (= 16.2.0.beta.
|
|
31
|
+
react_on_rails (= 16.2.0.beta.8)
|
|
31
32
|
|
|
32
33
|
GEM
|
|
33
34
|
remote: https://rubygems.org/
|
|
@@ -107,6 +108,12 @@ GEM
|
|
|
107
108
|
public_suffix (>= 2.0.2, < 7.0)
|
|
108
109
|
amazing_print (1.6.0)
|
|
109
110
|
ast (2.4.2)
|
|
111
|
+
async (2.34.0)
|
|
112
|
+
console (~> 1.29)
|
|
113
|
+
fiber-annotation
|
|
114
|
+
io-event (~> 1.11)
|
|
115
|
+
metrics (~> 0.12)
|
|
116
|
+
traces (~> 0.18)
|
|
110
117
|
base64 (0.2.0)
|
|
111
118
|
benchmark (0.4.0)
|
|
112
119
|
bigdecimal (3.1.9)
|
|
@@ -131,6 +138,10 @@ GEM
|
|
|
131
138
|
coderay (1.1.3)
|
|
132
139
|
concurrent-ruby (1.3.5)
|
|
133
140
|
connection_pool (2.5.0)
|
|
141
|
+
console (1.34.2)
|
|
142
|
+
fiber-annotation
|
|
143
|
+
fiber-local (~> 1.1)
|
|
144
|
+
json
|
|
134
145
|
coveralls (0.8.23)
|
|
135
146
|
json (>= 1.8, < 3)
|
|
136
147
|
simplecov (~> 0.16.1)
|
|
@@ -165,6 +176,9 @@ GEM
|
|
|
165
176
|
ffi (1.17.0-x86_64-darwin)
|
|
166
177
|
ffi (1.17.0-x86_64-linux-gnu)
|
|
167
178
|
ffi (1.17.0-x86_64-linux-musl)
|
|
179
|
+
fiber-annotation (0.2.0)
|
|
180
|
+
fiber-local (1.1.0)
|
|
181
|
+
fiber-storage
|
|
168
182
|
fiber-storage (1.0.0)
|
|
169
183
|
generator_spec (0.10.0)
|
|
170
184
|
activesupport (>= 3.0.0)
|
|
@@ -184,6 +198,7 @@ GEM
|
|
|
184
198
|
i18n (1.14.7)
|
|
185
199
|
concurrent-ruby (~> 1.0)
|
|
186
200
|
io-console (0.8.0)
|
|
201
|
+
io-event (1.14.2)
|
|
187
202
|
irb (1.15.1)
|
|
188
203
|
pp (>= 0.6.0)
|
|
189
204
|
rdoc (>= 4.0.0)
|
|
@@ -216,6 +231,7 @@ GEM
|
|
|
216
231
|
marcel (1.0.4)
|
|
217
232
|
matrix (0.4.2)
|
|
218
233
|
method_source (1.1.0)
|
|
234
|
+
metrics (0.15.0)
|
|
219
235
|
mini_mime (1.1.5)
|
|
220
236
|
mini_portile2 (2.8.8)
|
|
221
237
|
minitest (5.25.4)
|
|
@@ -447,6 +463,7 @@ GEM
|
|
|
447
463
|
tins (1.33.0)
|
|
448
464
|
bigdecimal
|
|
449
465
|
sync
|
|
466
|
+
traces (0.18.2)
|
|
450
467
|
turbolinks (5.2.1)
|
|
451
468
|
turbolinks-source (~> 5.2)
|
|
452
469
|
turbolinks-source (5.2.0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class PagesController < ApplicationController
|
|
3
|
+
class PagesController < ApplicationController # rubocop:disable Metrics/ClassLength
|
|
4
4
|
include ReactOnRailsPro::RSCPayloadRenderer
|
|
5
5
|
include RscPostsPageOverRedisHelper
|
|
6
6
|
|
|
@@ -85,8 +85,8 @@ class PagesController < ApplicationController
|
|
|
85
85
|
ensure
|
|
86
86
|
begin
|
|
87
87
|
redis&.close
|
|
88
|
-
rescue StandardError =>
|
|
89
|
-
Rails.logger.warn "Failed to close Redis: #{
|
|
88
|
+
rescue StandardError => e
|
|
89
|
+
Rails.logger.warn "Failed to close Redis: #{e.message}"
|
|
90
90
|
end
|
|
91
91
|
end
|
|
92
92
|
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
echo "Installing foreman..."
|
|
6
|
-
gem install foreman
|
|
7
|
-
fi
|
|
8
|
-
|
|
9
|
-
foreman start -f Procfile.dev
|
|
4
|
+
# This script calls the base dev script with a fixed route for the dummy app
|
|
5
|
+
exec File.join(__dir__, "../../../../lib/generators/react_on_rails/templates/base/base/bin/dev"), "--route=/", *ARGV
|
|
@@ -5,14 +5,14 @@ const Sentry = require('@sentry/node');
|
|
|
5
5
|
const { env } = process;
|
|
6
6
|
|
|
7
7
|
// Use this for package installation test:
|
|
8
|
-
const { reactOnRailsProNodeRenderer } = require('
|
|
8
|
+
const { reactOnRailsProNodeRenderer } = require('react-on-rails-pro-node-renderer');
|
|
9
9
|
|
|
10
10
|
Honeybadger.configure({
|
|
11
11
|
// This is a test account for React on Rails Pro. Substitute your own.
|
|
12
12
|
apiKey: 'a602365c',
|
|
13
13
|
environment: process.env.NODE_ENV ?? 'development',
|
|
14
14
|
});
|
|
15
|
-
require('
|
|
15
|
+
require('react-on-rails-pro-node-renderer/integrations/honeybadger').init();
|
|
16
16
|
|
|
17
17
|
// This is a test account for React on Rails Pro.
|
|
18
18
|
// Substitute your own DSN.
|
|
@@ -25,7 +25,7 @@ Sentry.init({
|
|
|
25
25
|
// Sentry recommends adjusting this value in production, or using tracesSampler for finer control
|
|
26
26
|
tracesSampleRate: 1.0,
|
|
27
27
|
});
|
|
28
|
-
require('
|
|
28
|
+
require('react-on-rails-pro-node-renderer/integrations/sentry').init({ tracing: true });
|
|
29
29
|
|
|
30
30
|
const config = {
|
|
31
31
|
// This is the default but avoids searching for the Rails root
|
|
@@ -69,7 +69,7 @@ Rails.application.configure do
|
|
|
69
69
|
config.active_support.deprecation = :notify
|
|
70
70
|
|
|
71
71
|
# Use default logging formatter so that PID and timestamp are not suppressed.
|
|
72
|
-
config.log_formatter =
|
|
72
|
+
config.log_formatter = Logger::Formatter.new
|
|
73
73
|
|
|
74
74
|
# Use a different logger for distributed setups.
|
|
75
75
|
# require 'syslog/logger'
|