react_on_rails 14.2.1 → 16.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/AI_AGENT_INSTRUCTIONS.md +63 -0
- data/CHANGELOG.md +564 -85
- data/CLAUDE.md +135 -0
- data/CODING_AGENTS.md +313 -0
- data/CONTRIBUTING.md +448 -37
- data/Gemfile.development_dependencies +6 -1
- data/Gemfile.lock +13 -4
- data/KUDOS.md +22 -1
- data/LICENSE.md +30 -4
- data/LICENSES/README.md +14 -0
- data/NEWS.md +48 -48
- data/PROJECTS.md +45 -40
- data/REACT-ON-RAILS-PRO-LICENSE.md +129 -0
- data/README.md +113 -42
- data/SUMMARY.md +62 -52
- data/TODO.md +135 -0
- data/bin/lefthook/check-trailing-newlines +38 -0
- data/bin/lefthook/get-changed-files +26 -0
- data/bin/lefthook/prettier-format +26 -0
- data/bin/lefthook/ruby-autofix +26 -0
- data/bin/lefthook/ruby-lint +27 -0
- data/eslint.config.ts +232 -0
- data/knip.ts +40 -6
- data/lib/generators/USAGE +4 -5
- data/lib/generators/react_on_rails/USAGE +65 -0
- data/lib/generators/react_on_rails/base_generator.rb +276 -62
- data/lib/generators/react_on_rails/dev_tests_generator.rb +1 -0
- data/lib/generators/react_on_rails/generator_helper.rb +35 -1
- data/lib/generators/react_on_rails/generator_messages.rb +138 -17
- data/lib/generators/react_on_rails/install_generator.rb +474 -26
- data/lib/generators/react_on_rails/react_no_redux_generator.rb +19 -6
- data/lib/generators/react_on_rails/react_with_redux_generator.rb +110 -18
- data/lib/generators/react_on_rails/templates/.eslintrc +1 -1
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev +5 -0
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-prod-assets +8 -0
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static-assets +2 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -5
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +2 -2
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorldServer.js +1 -1
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/server-bundle.js +1 -8
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx +21 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.tsx +25 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css +4 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.jsx +5 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.tsx +5 -0
- data/lib/generators/react_on_rails/templates/base/base/app/views/hello_world/index.html.erb.tt +1 -1
- data/lib/generators/react_on_rails/templates/base/base/app/views/layouts/hello_world.html.erb +4 -2
- data/lib/generators/react_on_rails/templates/base/base/babel.config.js.tt +5 -2
- data/lib/generators/react_on_rails/templates/base/base/bin/dev +34 -0
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +14 -5
- data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +76 -7
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt +1 -1
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +6 -10
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/production.js.tt +2 -2
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +3 -2
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/test.js.tt +2 -2
- data/lib/generators/react_on_rails/templates/dev_tests/spec/system/hello_world_spec.rb +0 -2
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.ts +18 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -6
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +4 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.tsx +24 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/constants/helloWorldConstants.ts +6 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/containers/HelloWorldContainer.ts +20 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.js +1 -1
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.ts +22 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.tsx +23 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.jsx +5 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.tsx +5 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/store/helloWorldStore.ts +18 -0
- data/lib/react_on_rails/configuration.rb +141 -57
- data/lib/react_on_rails/controller.rb +6 -2
- data/lib/react_on_rails/dev/file_manager.rb +78 -0
- data/lib/react_on_rails/dev/pack_generator.rb +27 -0
- data/lib/react_on_rails/dev/process_manager.rb +61 -0
- data/lib/react_on_rails/dev/server_manager.rb +487 -0
- data/lib/react_on_rails/dev.rb +20 -0
- data/lib/react_on_rails/doctor.rb +1149 -0
- data/lib/react_on_rails/engine.rb +6 -0
- data/lib/react_on_rails/git_utils.rb +12 -2
- data/lib/react_on_rails/helper.rb +176 -74
- data/lib/react_on_rails/json_parse_error.rb +6 -1
- data/lib/react_on_rails/packer_utils.rb +61 -71
- data/lib/react_on_rails/packs_generator.rb +221 -19
- data/lib/react_on_rails/prerender_error.rb +4 -0
- data/lib/react_on_rails/pro/NOTICE +21 -0
- data/lib/react_on_rails/pro/helper.rb +122 -0
- data/lib/react_on_rails/pro/utils.rb +53 -0
- data/lib/react_on_rails/react_component/render_options.rb +38 -6
- data/lib/react_on_rails/server_rendering_js_code.rb +0 -1
- data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +12 -5
- data/lib/react_on_rails/system_checker.rb +659 -0
- data/lib/react_on_rails/test_helper/webpack_assets_compiler.rb +1 -1
- data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +6 -4
- data/lib/react_on_rails/test_helper.rb +2 -3
- data/lib/react_on_rails/utils.rb +139 -43
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails/version_checker.rb +14 -20
- data/lib/react_on_rails/version_syntax_converter.rb +1 -1
- data/lib/react_on_rails.rb +1 -0
- data/lib/tasks/assets.rake +1 -1
- data/lib/tasks/doctor.rake +48 -0
- data/lib/tasks/generate_packs.rake +158 -1
- data/react_on_rails.gemspec +1 -0
- data/tsconfig.eslint.json +6 -0
- data/tsconfig.json +5 -3
- metadata +63 -14
- data/REACT-ON-RAILS-PRO-LICENSE +0 -95
- data/lib/generators/react_on_rails/adapt_for_older_shakapacker_generator.rb +0 -41
- data/lib/generators/react_on_rails/bin/dev +0 -30
- data/lib/generators/react_on_rails/bin/dev-static +0 -30
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static.tt +0 -9
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt +0 -5
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/registration.js.tt +0 -8
- /data/lib/generators/react_on_rails/templates/base/base/config/webpack/{webpackConfig.js.tt → generateWebpackConfigs.js.tt} +0 -0
- /data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/{HelloWorldApp.jsx → HelloWorldApp.client.jsx} +0 -0
@@ -8,5 +8,11 @@ module ReactOnRails
|
|
8
8
|
VersionChecker.build.log_if_gem_and_node_package_versions_differ
|
9
9
|
ReactOnRails::ServerRenderingPool.reset_pool
|
10
10
|
end
|
11
|
+
|
12
|
+
rake_tasks do
|
13
|
+
load File.expand_path("../tasks/generate_packs.rake", __dir__)
|
14
|
+
load File.expand_path("../tasks/assets.rake", __dir__)
|
15
|
+
load File.expand_path("../tasks/locale.rake", __dir__)
|
16
|
+
end
|
11
17
|
end
|
12
18
|
end
|
@@ -11,9 +11,19 @@ module ReactOnRails
|
|
11
11
|
return false if git_installed && status&.empty?
|
12
12
|
|
13
13
|
error = if git_installed
|
14
|
-
|
14
|
+
<<~MSG.strip
|
15
|
+
You have uncommitted changes. Please commit or stash them before continuing.
|
16
|
+
|
17
|
+
The React on Rails generator creates many new files and it's important to keep
|
18
|
+
your existing changes separate from the generated code for easier review.
|
19
|
+
MSG
|
15
20
|
else
|
16
|
-
|
21
|
+
<<~MSG.strip
|
22
|
+
Git is not installed. Please install Git and commit your changes before continuing.
|
23
|
+
|
24
|
+
The React on Rails generator creates many new files and version control helps
|
25
|
+
track what was generated versus your existing code.
|
26
|
+
MSG
|
17
27
|
end
|
18
28
|
message_handler.add_error(error)
|
19
29
|
true
|
@@ -11,10 +11,12 @@ require "addressable/uri"
|
|
11
11
|
require "react_on_rails/utils"
|
12
12
|
require "react_on_rails/json_output"
|
13
13
|
require "active_support/concern"
|
14
|
+
require "react_on_rails/pro/helper"
|
14
15
|
|
15
16
|
module ReactOnRails
|
16
17
|
module Helper
|
17
18
|
include ReactOnRails::Utils::Required
|
19
|
+
include ReactOnRails::Pro::Helper
|
18
20
|
|
19
21
|
COMPONENT_HTML_KEY = "componentHtml"
|
20
22
|
|
@@ -32,7 +34,7 @@ module ReactOnRails
|
|
32
34
|
#
|
33
35
|
# Exposing the react_component_name is necessary to both a plain ReactComponent as well as
|
34
36
|
# a generator:
|
35
|
-
# See README.md for how to "register" your
|
37
|
+
# See README.md for how to "register" your React components.
|
36
38
|
# See spec/dummy/client/app/packs/server-bundle.js and
|
37
39
|
# spec/dummy/client/app/packs/client-bundle.js for examples of this.
|
38
40
|
#
|
@@ -61,12 +63,13 @@ module ReactOnRails
|
|
61
63
|
|
62
64
|
case server_rendered_html
|
63
65
|
when String
|
64
|
-
build_react_component_result_for_server_rendered_string(
|
66
|
+
html = build_react_component_result_for_server_rendered_string(
|
65
67
|
server_rendered_html: server_rendered_html,
|
66
68
|
component_specification_tag: internal_result[:tag],
|
67
69
|
console_script: console_script,
|
68
70
|
render_options: render_options
|
69
71
|
)
|
72
|
+
html.html_safe
|
70
73
|
when Hash
|
71
74
|
msg = <<~MSG
|
72
75
|
Use react_component_hash (not react_component) to return a Hash to your ruby view code. See
|
@@ -119,34 +122,74 @@ module ReactOnRails
|
|
119
122
|
# @option options [Hash] :props Props to pass to the react component
|
120
123
|
# @option options [String] :dom_id DOM ID of the component container
|
121
124
|
# @option options [Hash] :html_options Options passed to content_tag
|
122
|
-
# @option options [Boolean] :prerender Set to false to disable server-side rendering
|
123
125
|
# @option options [Boolean] :trace Set to true to add extra debugging information to the HTML
|
124
126
|
# @option options [Boolean] :raise_on_prerender_error Set to true to raise exceptions during server-side rendering
|
125
127
|
# Any other options are passed to the content tag, including the id.
|
126
128
|
def stream_react_component(component_name, options = {})
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
raise ReactOnRails::Error,
|
134
|
-
"You must call stream_view_containing_react_components to render the view containing the react component"
|
129
|
+
# stream_react_component doesn't have the prerender option
|
130
|
+
# Because setting prerender to false is equivalent to calling react_component with prerender: false
|
131
|
+
options[:prerender] = true
|
132
|
+
options = options.merge(immediate_hydration: true) unless options.key?(:immediate_hydration)
|
133
|
+
run_stream_inside_fiber do
|
134
|
+
internal_stream_react_component(component_name, options)
|
135
135
|
end
|
136
|
+
end
|
136
137
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
138
|
+
# Renders the React Server Component (RSC) payload for a given component. This helper generates
|
139
|
+
# a special format designed by React for serializing server components and transmitting them
|
140
|
+
# to the client.
|
141
|
+
#
|
142
|
+
# @return [String] Returns a Newline Delimited JSON (NDJSON) stream where each line contains a JSON object with:
|
143
|
+
# - html: The RSC payload containing the rendered server components and client component references
|
144
|
+
# - consoleReplayScript: JavaScript to replay server-side console logs in the client
|
145
|
+
# - hasErrors: Boolean indicating if any errors occurred during rendering
|
146
|
+
# - isShellReady: Boolean indicating if the initial shell is ready for hydration
|
147
|
+
#
|
148
|
+
# Example NDJSON stream:
|
149
|
+
# {"html":"<RSC Payload>","consoleReplayScript":"","hasErrors":false,"isShellReady":true}
|
150
|
+
# {"html":"<RSC Payload>","consoleReplayScript":"console.log('Loading...')","hasErrors":false,"isShellReady":true}
|
151
|
+
#
|
152
|
+
# The RSC payload within the html field contains:
|
153
|
+
# - The component's rendered output from the server
|
154
|
+
# - References to client components that need hydration
|
155
|
+
# - Data props passed to client components
|
156
|
+
#
|
157
|
+
# @param component_name [String] The name of the React component to render. This component should
|
158
|
+
# be a server component or a mixed component tree containing both server and client components.
|
159
|
+
#
|
160
|
+
# @param options [Hash] Options for rendering the component
|
161
|
+
# @option options [Hash] :props Props to pass to the component (default: {})
|
162
|
+
# @option options [Boolean] :trace Enable tracing for debugging (default: false)
|
163
|
+
# @option options [String] :id Custom DOM ID for the component container (optional)
|
164
|
+
#
|
165
|
+
# @example Basic usage with a server component
|
166
|
+
# <%= rsc_payload_react_component("ReactServerComponentPage") %>
|
167
|
+
#
|
168
|
+
# @example With props and tracing enabled
|
169
|
+
# <%= rsc_payload_react_component("RSCPostsPage",
|
170
|
+
# props: { artificialDelay: 1000 },
|
171
|
+
# trace: true) %>
|
172
|
+
#
|
173
|
+
# @note This helper requires React Server Components support to be enabled in your configuration:
|
174
|
+
# ReactOnRailsPro.configure do |config|
|
175
|
+
# config.enable_rsc_support = true
|
176
|
+
# end
|
177
|
+
#
|
178
|
+
# @raise [ReactOnRailsPro::Error] if RSC support is not enabled in configuration
|
179
|
+
#
|
180
|
+
# @note You don't have to deal directly with this helper function - it's used internally by the
|
181
|
+
# `rsc_payload_route` helper function. The returned data from this function is used internally by
|
182
|
+
# components registered using the `registerServerComponent` function. Don't use it unless you need
|
183
|
+
# more control over the RSC payload generation. To know more about RSC payload, see the following link:
|
184
|
+
# @see https://www.shakacode.com/react-on-rails-pro/docs/how-react-server-components-works.md
|
185
|
+
# for technical details about the RSC payload format
|
186
|
+
def rsc_payload_react_component(component_name, options = {})
|
187
|
+
# rsc_payload_react_component doesn't have the prerender option
|
188
|
+
# Because setting prerender to false will not do anything
|
189
|
+
options[:prerender] = true
|
190
|
+
run_stream_inside_fiber do
|
191
|
+
internal_rsc_payload_react_component(component_name, options)
|
142
192
|
end
|
143
|
-
|
144
|
-
@rorp_rendering_fibers << rendering_fiber
|
145
|
-
|
146
|
-
# return the first chunk of the fiber
|
147
|
-
# It contains the initial html of the component
|
148
|
-
# all updates will be appended to the stream sent to browser
|
149
|
-
rendering_fiber.resume
|
150
193
|
end
|
151
194
|
|
152
195
|
# react_component_hash is used to return multiple HTML strings for server rendering, such as for
|
@@ -168,6 +211,7 @@ module ReactOnRails
|
|
168
211
|
#
|
169
212
|
def react_component_hash(component_name, options = {})
|
170
213
|
options[:prerender] = true
|
214
|
+
|
171
215
|
internal_result = internal_react_component(component_name, options)
|
172
216
|
server_rendered_html = internal_result[:result]["html"]
|
173
217
|
console_script = internal_result[:result]["consoleReplayScript"]
|
@@ -184,6 +228,7 @@ module ReactOnRails
|
|
184
228
|
console_script: console_script,
|
185
229
|
render_options: render_options
|
186
230
|
)
|
231
|
+
|
187
232
|
else
|
188
233
|
msg = <<~MSG
|
189
234
|
Render-Function used by react_component_hash for #{component_name} is expected to return
|
@@ -207,19 +252,22 @@ module ReactOnRails
|
|
207
252
|
# props: Ruby Hash or JSON string which contains the properties to pass to the redux store.
|
208
253
|
# Options
|
209
254
|
# defer: false -- pass as true if you wish to render this below your component.
|
210
|
-
|
255
|
+
# immediate_hydration: false -- React on Rails Pro (licensed) feature. Pass as true if you wish to
|
256
|
+
# hydrate this store immediately instead of waiting for the page to load.
|
257
|
+
def redux_store(store_name, props: {}, defer: false, immediate_hydration: nil)
|
258
|
+
immediate_hydration = ReactOnRails.configuration.immediate_hydration if immediate_hydration.nil?
|
259
|
+
|
211
260
|
redux_store_data = { store_name: store_name,
|
212
|
-
props: props
|
261
|
+
props: props,
|
262
|
+
immediate_hydration: immediate_hydration }
|
213
263
|
if defer
|
214
|
-
|
215
|
-
@registered_stores_defer_render << redux_store_data
|
264
|
+
registered_stores_defer_render << redux_store_data
|
216
265
|
"YOU SHOULD NOT SEE THIS ON YOUR VIEW -- Uses as a code block, like <% redux_store %> " \
|
217
266
|
"and not <%= redux store %>"
|
218
267
|
else
|
219
|
-
|
220
|
-
@registered_stores << redux_store_data
|
268
|
+
registered_stores << redux_store_data
|
221
269
|
result = render_redux_store_data(redux_store_data)
|
222
|
-
prepend_render_rails_context(result)
|
270
|
+
prepend_render_rails_context(result).html_safe
|
223
271
|
end
|
224
272
|
end
|
225
273
|
|
@@ -229,9 +277,9 @@ module ReactOnRails
|
|
229
277
|
# client side rendering of this hydration data, which is a hidden div with a matching class
|
230
278
|
# that contains a data props.
|
231
279
|
def redux_store_hydration_data
|
232
|
-
return if
|
280
|
+
return if registered_stores_defer_render.blank?
|
233
281
|
|
234
|
-
|
282
|
+
registered_stores_defer_render.reduce(+"") do |accum, redux_store_data|
|
235
283
|
accum << render_redux_store_data(redux_store_data)
|
236
284
|
end.html_safe
|
237
285
|
end
|
@@ -292,7 +340,7 @@ module ReactOnRails
|
|
292
340
|
|
293
341
|
html = result["html"]
|
294
342
|
console_log_script = result["consoleLogScript"]
|
295
|
-
raw("#{html}#{render_options.replay_console
|
343
|
+
raw("#{html}#{console_log_script if render_options.replay_console}")
|
296
344
|
rescue ExecJS::ProgramError => err
|
297
345
|
raise ReactOnRails::PrerenderError.new(component_name: "N/A (server_render_js called)",
|
298
346
|
err: err,
|
@@ -321,6 +369,7 @@ module ReactOnRails
|
|
321
369
|
# ALERT: Keep in sync with node_package/src/types/index.ts for the properties of RailsContext
|
322
370
|
@rails_context ||= begin
|
323
371
|
result = {
|
372
|
+
componentRegistryTimeout: ReactOnRails.configuration.component_registry_timeout,
|
324
373
|
railsEnv: Rails.env,
|
325
374
|
inMailer: in_mailer?,
|
326
375
|
# Locale settings
|
@@ -330,8 +379,14 @@ module ReactOnRails
|
|
330
379
|
# TODO: v13 just use the version if existing
|
331
380
|
rorPro: ReactOnRails::Utils.react_on_rails_pro?
|
332
381
|
}
|
382
|
+
|
333
383
|
if ReactOnRails::Utils.react_on_rails_pro?
|
334
384
|
result[:rorProVersion] = ReactOnRails::Utils.react_on_rails_pro_version
|
385
|
+
|
386
|
+
if ReactOnRails::Utils.rsc_support_enabled?
|
387
|
+
rsc_payload_url = ReactOnRailsPro.configuration.rsc_payload_generation_url_path
|
388
|
+
result[:rscPayloadGenerationUrlPath] = rsc_payload_url
|
389
|
+
end
|
335
390
|
end
|
336
391
|
|
337
392
|
if defined?(request) && request.present?
|
@@ -352,7 +407,7 @@ module ReactOnRails
|
|
352
407
|
result.merge!(
|
353
408
|
# URL settings
|
354
409
|
href: uri.to_s,
|
355
|
-
location: "#{uri.path}#{
|
410
|
+
location: "#{uri.path}#{"?#{uri.query}" if uri.query.present?}",
|
356
411
|
scheme: uri.scheme, # http
|
357
412
|
host: uri.host, # foo.com
|
358
413
|
port: uri.port,
|
@@ -379,17 +434,68 @@ module ReactOnRails
|
|
379
434
|
is_component_pack_present = File.exist?(generated_components_pack_path(react_component_name))
|
380
435
|
raise_missing_autoloaded_bundle(react_component_name) unless is_component_pack_present
|
381
436
|
end
|
382
|
-
|
383
|
-
|
437
|
+
|
438
|
+
options = { defer: ReactOnRails.configuration.generated_component_packs_loading_strategy == :defer }
|
439
|
+
# Old versions of Shakapacker don't support async script tags.
|
440
|
+
# ReactOnRails.configure already validates if async loading is supported by the installed Shakapacker version.
|
441
|
+
# Therefore, we only need to pass the async option if the loading strategy is explicitly set to :async
|
442
|
+
options[:async] = true if ReactOnRails.configuration.generated_component_packs_loading_strategy == :async
|
443
|
+
append_javascript_pack_tag("generated/#{react_component_name}", **options)
|
384
444
|
append_stylesheet_pack_tag("generated/#{react_component_name}")
|
385
445
|
end
|
386
446
|
|
387
447
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
388
448
|
|
389
|
-
|
449
|
+
def run_stream_inside_fiber
|
450
|
+
unless ReactOnRails::Utils.react_on_rails_pro?
|
451
|
+
raise ReactOnRails::Error,
|
452
|
+
"You must use React on Rails Pro to use the stream_react_component method."
|
453
|
+
end
|
454
|
+
|
455
|
+
if @rorp_rendering_fibers.nil?
|
456
|
+
raise ReactOnRails::Error,
|
457
|
+
"You must call stream_view_containing_react_components to render the view containing the react component"
|
458
|
+
end
|
459
|
+
|
460
|
+
rendering_fiber = Fiber.new do
|
461
|
+
stream = yield
|
462
|
+
stream.each_chunk do |chunk|
|
463
|
+
Fiber.yield chunk
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
@rorp_rendering_fibers << rendering_fiber
|
468
|
+
|
469
|
+
# return the first chunk of the fiber
|
470
|
+
# It contains the initial html of the component
|
471
|
+
# all updates will be appended to the stream sent to browser
|
472
|
+
rendering_fiber.resume
|
473
|
+
end
|
474
|
+
|
475
|
+
def registered_stores
|
476
|
+
@registered_stores ||= []
|
477
|
+
end
|
478
|
+
|
479
|
+
def registered_stores_defer_render
|
480
|
+
@registered_stores_defer_render ||= []
|
481
|
+
end
|
482
|
+
|
483
|
+
def registered_stores_including_deferred
|
484
|
+
registered_stores + registered_stores_defer_render
|
485
|
+
end
|
486
|
+
|
487
|
+
def create_render_options(react_component_name, options)
|
488
|
+
# If no store dependencies are passed, default to all registered stores up till now
|
489
|
+
unless options.key?(:store_dependencies)
|
490
|
+
store_dependencies = registered_stores_including_deferred.map { |store| store[:store_name] }
|
491
|
+
options = options.merge(store_dependencies: store_dependencies.presence)
|
492
|
+
end
|
493
|
+
ReactOnRails::ReactComponent::RenderOptions.new(react_component_name: react_component_name,
|
494
|
+
options: options)
|
495
|
+
end
|
390
496
|
|
391
497
|
def internal_stream_react_component(component_name, options = {})
|
392
|
-
options = options.merge(
|
498
|
+
options = options.merge(render_mode: :html_streaming)
|
393
499
|
result = internal_react_component(component_name, options)
|
394
500
|
build_react_component_result_for_server_streamed_content(
|
395
501
|
rendered_html_stream: result[:result],
|
@@ -398,6 +504,15 @@ module ReactOnRails
|
|
398
504
|
)
|
399
505
|
end
|
400
506
|
|
507
|
+
def internal_rsc_payload_react_component(react_component_name, options = {})
|
508
|
+
options = options.merge(render_mode: :rsc_payload_streaming)
|
509
|
+
render_options = create_render_options(react_component_name, options)
|
510
|
+
json_stream = server_rendered_react_component(render_options)
|
511
|
+
json_stream.transform do |chunk|
|
512
|
+
"#{chunk.to_json}\n".html_safe
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
401
516
|
def generated_components_pack_path(component_name)
|
402
517
|
"#{ReactOnRails::PackerUtils.packer_source_entry_path}/generated/#{component_name}.js"
|
403
518
|
end
|
@@ -489,14 +604,13 @@ module ReactOnRails
|
|
489
604
|
)
|
490
605
|
end
|
491
606
|
|
492
|
-
def compose_react_component_html_with_spec_and_console(component_specification_tag, rendered_output,
|
607
|
+
def compose_react_component_html_with_spec_and_console(component_specification_tag, rendered_output,
|
608
|
+
console_script)
|
493
609
|
# IMPORTANT: Ensure that we mark string as html_safe to avoid escaping.
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
HTML
|
499
|
-
html_content.strip.html_safe
|
610
|
+
added_html = "#{component_specification_tag}\n#{console_script}".strip
|
611
|
+
added_html = added_html.present? ? "\n#{added_html}" : ""
|
612
|
+
|
613
|
+
"#{rendered_output}#{added_html}".html_safe
|
500
614
|
end
|
501
615
|
|
502
616
|
def rails_context_if_not_already_rendered
|
@@ -514,7 +628,9 @@ module ReactOnRails
|
|
514
628
|
|
515
629
|
# prepend the rails_context if not yet applied
|
516
630
|
def prepend_render_rails_context(render_value)
|
517
|
-
|
631
|
+
rails_context_content = rails_context_if_not_already_rendered
|
632
|
+
rails_context_content = rails_context_content.present? ? "#{rails_context_content}\n" : ""
|
633
|
+
"#{rails_context_content}#{render_value}".html_safe
|
518
634
|
end
|
519
635
|
|
520
636
|
def internal_react_component(react_component_name, options = {})
|
@@ -525,26 +641,11 @@ module ReactOnRails
|
|
525
641
|
# (re-hydrate the data). This enables react rendered on the client to see that the
|
526
642
|
# server has already rendered the HTML.
|
527
643
|
|
528
|
-
render_options =
|
529
|
-
options: options)
|
644
|
+
render_options = create_render_options(react_component_name, options)
|
530
645
|
|
531
646
|
# Setup the page_loaded_js, which is the same regardless of prerendering or not!
|
532
647
|
# The reason is that React is smart about not doing extra work if the server rendering did its job.
|
533
|
-
component_specification_tag =
|
534
|
-
json_safe_and_pretty(render_options.client_props).html_safe,
|
535
|
-
type: "application/json",
|
536
|
-
class: "js-react-on-rails-component",
|
537
|
-
"data-component-name" => render_options.react_component_name,
|
538
|
-
"data-trace" => (render_options.trace ? true : nil),
|
539
|
-
"data-dom-id" => render_options.dom_id)
|
540
|
-
|
541
|
-
if render_options.force_load
|
542
|
-
component_specification_tag.concat(
|
543
|
-
content_tag(:script, %(
|
544
|
-
ReactOnRails.reactOnRailsComponentLoaded('#{render_options.dom_id}');
|
545
|
-
).html_safe)
|
546
|
-
)
|
547
|
-
end
|
648
|
+
component_specification_tag = generate_component_script(render_options)
|
548
649
|
|
549
650
|
load_pack_for_generated_component(react_component_name, render_options)
|
550
651
|
# Create the HTML rendering part
|
@@ -558,12 +659,9 @@ ReactOnRails.reactOnRailsComponentLoaded('#{render_options.dom_id}');
|
|
558
659
|
end
|
559
660
|
|
560
661
|
def render_redux_store_data(redux_store_data)
|
561
|
-
|
562
|
-
json_safe_and_pretty(redux_store_data[:props]).html_safe,
|
563
|
-
type: "application/json",
|
564
|
-
"data-js-react-on-rails-store" => redux_store_data[:store_name].html_safe)
|
662
|
+
store_hydration_data = generate_store_script(redux_store_data)
|
565
663
|
|
566
|
-
prepend_render_rails_context(
|
664
|
+
prepend_render_rails_context(store_hydration_data)
|
567
665
|
end
|
568
666
|
|
569
667
|
def props_string(props)
|
@@ -620,7 +718,7 @@ ReactOnRails.reactOnRailsComponentLoaded('#{render_options.dom_id}');
|
|
620
718
|
js_code = ReactOnRails::ServerRenderingJsCode.server_rendering_component_js_code(
|
621
719
|
props_string: props_string(props).gsub("\u2028", '\u2028').gsub("\u2029", '\u2029'),
|
622
720
|
rails_context: rails_context(server_side: true).to_json,
|
623
|
-
redux_stores: initialize_redux_stores,
|
721
|
+
redux_stores: initialize_redux_stores(render_options),
|
624
722
|
react_component_name: react_component_name,
|
625
723
|
render_options: render_options
|
626
724
|
)
|
@@ -636,7 +734,7 @@ ReactOnRails.reactOnRailsComponentLoaded('#{render_options.dom_id}');
|
|
636
734
|
js_code: js_code)
|
637
735
|
end
|
638
736
|
|
639
|
-
if render_options.
|
737
|
+
if render_options.streaming?
|
640
738
|
result.transform do |chunk_json_result|
|
641
739
|
if should_raise_streaming_prerender_error?(chunk_json_result, render_options)
|
642
740
|
raise_prerender_error(chunk_json_result, react_component_name, props, js_code)
|
@@ -651,17 +749,20 @@ ReactOnRails.reactOnRailsComponentLoaded('#{render_options.dom_id}');
|
|
651
749
|
result
|
652
750
|
end
|
653
751
|
|
654
|
-
def initialize_redux_stores
|
752
|
+
def initialize_redux_stores(render_options)
|
655
753
|
result = +<<-JS
|
656
754
|
ReactOnRails.clearHydratedStores();
|
657
755
|
JS
|
658
756
|
|
659
|
-
|
757
|
+
store_dependencies = render_options.store_dependencies
|
758
|
+
return result unless store_dependencies.present?
|
660
759
|
|
661
760
|
declarations = +"var reduxProps, store, storeGenerator;\n"
|
662
|
-
|
761
|
+
store_objects = registered_stores_including_deferred.select do |store|
|
762
|
+
store_dependencies.include?(store[:store_name])
|
763
|
+
end
|
663
764
|
|
664
|
-
result <<
|
765
|
+
result << store_objects.each_with_object(declarations) do |redux_store_data, memo|
|
665
766
|
store_name = redux_store_data[:store_name]
|
666
767
|
props = props_string(redux_store_data[:props])
|
667
768
|
memo << <<-JS.strip_heredoc
|
@@ -687,6 +788,7 @@ ReactOnRails.reactOnRailsComponentLoaded('#{render_options.dom_id}');
|
|
687
788
|
|
688
789
|
if defined?(ScoutApm)
|
689
790
|
include ScoutApm::Tracer
|
791
|
+
|
690
792
|
instrument_method :react_component, type: "ReactOnRails", name: "react_component"
|
691
793
|
instrument_method :react_component_hash, type: "ReactOnRails", name: "react_component_hash"
|
692
794
|
end
|
@@ -7,7 +7,12 @@ module ReactOnRails
|
|
7
7
|
def initialize(parse_error:, json:)
|
8
8
|
@json = json
|
9
9
|
@original_error = parse_error
|
10
|
-
|
10
|
+
message = <<~MSG
|
11
|
+
#{parse_error.message}
|
12
|
+
|
13
|
+
#{Utils.default_troubleshooting_section}
|
14
|
+
MSG
|
15
|
+
super(message)
|
11
16
|
end
|
12
17
|
|
13
18
|
def to_honeybadger_context
|