react_on_rails 16.1.2 → 16.2.0
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/.rspec +2 -0
- data/.rubocop.yml +85 -0
- data/Gemfile.development_dependencies +8 -7
- data/Gemfile.lock +158 -119
- data/Steepfile +56 -0
- data/lib/generators/react_on_rails/base_generator.rb +43 -120
- data/lib/generators/react_on_rails/dev_tests_generator.rb +2 -1
- data/lib/generators/react_on_rails/generator_helper.rb +102 -2
- data/lib/generators/react_on_rails/install_generator.rb +36 -156
- data/lib/generators/react_on_rails/js_dependency_manager.rb +383 -0
- data/lib/generators/react_on_rails/templates/base/base/.dev-services.yml.example +76 -0
- data/lib/generators/react_on_rails/templates/base/base/bin/shakapacker-precompile-hook +30 -0
- data/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler +141 -0
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +44 -45
- data/lib/generators/react_on_rails/templates/base/base/config/{shakapacker.yml → shakapacker.yml.tt} +28 -3
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +15 -9
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +42 -6
- data/lib/react_on_rails/configuration.rb +149 -32
- data/lib/react_on_rails/controller.rb +3 -3
- data/lib/react_on_rails/dev/pack_generator.rb +168 -2
- data/lib/react_on_rails/dev/process_manager.rb +136 -14
- data/lib/react_on_rails/dev/server_manager.rb +194 -26
- data/lib/react_on_rails/dev/service_checker.rb +200 -0
- data/lib/react_on_rails/doctor.rb +341 -12
- data/lib/react_on_rails/engine.rb +75 -1
- data/lib/react_on_rails/git_utils.rb +3 -1
- data/lib/react_on_rails/helper.rb +70 -192
- data/lib/react_on_rails/locales/base.rb +17 -5
- data/lib/react_on_rails/packer_utils.rb +79 -2
- data/lib/react_on_rails/packs_generator.rb +57 -39
- data/lib/react_on_rails/prerender_error.rb +74 -17
- data/lib/react_on_rails/pro_helper.rb +64 -0
- data/lib/react_on_rails/react_component/render_options.rb +7 -7
- data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +2 -5
- data/lib/react_on_rails/smart_error.rb +326 -0
- data/lib/react_on_rails/system_checker.rb +8 -9
- data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +16 -7
- data/lib/react_on_rails/utils.rb +241 -55
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails/version_checker.rb +383 -35
- data/lib/tasks/generate_packs.rake +12 -6
- data/lib/tasks/locale.rake +6 -1
- data/rakelib/docker.rake +26 -0
- data/rakelib/dummy_apps.rake +30 -0
- data/rakelib/example_type.rb +121 -0
- data/rakelib/examples_config.yml +52 -0
- data/rakelib/lint.rake +52 -0
- data/rakelib/node_package.rake +15 -0
- data/rakelib/rbs.rake +70 -0
- data/rakelib/run_rspec.rake +223 -0
- data/rakelib/shakapacker_examples.rake +171 -0
- data/rakelib/task_helpers.rb +134 -0
- data/rakelib/update_changelog.rake +73 -0
- data/react_on_rails.gemspec +4 -3
- data/sig/README.md +52 -0
- data/sig/react_on_rails/configuration.rbs +96 -0
- data/sig/react_on_rails/controller.rbs +15 -0
- data/sig/react_on_rails/dev/file_manager.rbs +15 -0
- data/sig/react_on_rails/dev/pack_generator.rbs +19 -0
- data/sig/react_on_rails/dev/process_manager.rbs +22 -0
- data/sig/react_on_rails/dev/server_manager.rbs +39 -0
- data/sig/react_on_rails/dev/service_checker.rbs +22 -0
- data/sig/react_on_rails/error.rbs +4 -0
- data/sig/react_on_rails/generators/js_dependency_manager.rbs +123 -0
- data/sig/react_on_rails/git_utils.rbs +8 -0
- data/sig/react_on_rails/helper.rbs +65 -0
- data/sig/react_on_rails/json_parse_error.rbs +10 -0
- data/sig/react_on_rails/locales.rbs +46 -0
- data/sig/react_on_rails/packer_utils.rbs +15 -0
- data/sig/react_on_rails/prerender_error.rbs +21 -0
- data/sig/react_on_rails/server_rendering_pool.rbs +12 -0
- data/sig/react_on_rails/smart_error.rbs +28 -0
- data/sig/react_on_rails/test_helper.rbs +11 -0
- data/sig/react_on_rails/utils.rbs +34 -0
- data/sig/react_on_rails/version_checker.rbs +12 -0
- data/sig/react_on_rails.rbs +17 -0
- metadata +49 -32
- data/AI_AGENT_INSTRUCTIONS.md +0 -63
- data/CHANGELOG.md +0 -1836
- data/CLAUDE.md +0 -135
- data/CODING_AGENTS.md +0 -313
- data/CONTRIBUTING.md +0 -668
- data/Dockerfile_tests +0 -12
- data/KUDOS.md +0 -114
- data/LICENSE.md +0 -47
- data/LICENSES/README.md +0 -14
- data/NEWS.md +0 -62
- data/PROJECTS.md +0 -63
- data/REACT-ON-RAILS-PRO-LICENSE.md +0 -129
- data/README.md +0 -217
- data/SUMMARY.md +0 -88
- data/TODO.md +0 -135
- data/bin/lefthook/check-trailing-newlines +0 -38
- data/bin/lefthook/get-changed-files +0 -26
- data/bin/lefthook/prettier-format +0 -26
- data/bin/lefthook/ruby-autofix +0 -26
- data/bin/lefthook/ruby-lint +0 -27
- data/docker-compose.yml +0 -11
- data/eslint.config.ts +0 -232
- data/knip.ts +0 -114
- data/lib/react_on_rails/pro/NOTICE +0 -21
- data/lib/react_on_rails/pro/helper.rb +0 -122
- data/lib/react_on_rails/pro/utils.rb +0 -53
- data/tsconfig.eslint.json +0 -6
- data/tsconfig.json +0 -19
|
@@ -7,16 +7,17 @@
|
|
|
7
7
|
# 1. The white spacing in this file matters!
|
|
8
8
|
# 2. Keep all #{some_var} fully to the left so that all indentation is done evenly in that var
|
|
9
9
|
require "react_on_rails/prerender_error"
|
|
10
|
+
require "react_on_rails/smart_error"
|
|
10
11
|
require "addressable/uri"
|
|
11
12
|
require "react_on_rails/utils"
|
|
12
13
|
require "react_on_rails/json_output"
|
|
13
14
|
require "active_support/concern"
|
|
14
|
-
require "react_on_rails/
|
|
15
|
+
require "react_on_rails/pro_helper"
|
|
15
16
|
|
|
16
17
|
module ReactOnRails
|
|
17
18
|
module Helper
|
|
18
19
|
include ReactOnRails::Utils::Required
|
|
19
|
-
include ReactOnRails::
|
|
20
|
+
include ReactOnRails::ProHelper
|
|
20
21
|
|
|
21
22
|
COMPONENT_HTML_KEY = "componentHtml"
|
|
22
23
|
|
|
@@ -94,104 +95,6 @@ module ReactOnRails
|
|
|
94
95
|
end
|
|
95
96
|
end
|
|
96
97
|
|
|
97
|
-
# Streams a server-side rendered React component using React's `renderToPipeableStream`.
|
|
98
|
-
# Supports React 18 features like Suspense, concurrent rendering, and selective hydration.
|
|
99
|
-
# Enables progressive rendering and improved performance for large components.
|
|
100
|
-
#
|
|
101
|
-
# Note: This function can only be used with React on Rails Pro.
|
|
102
|
-
# The view that uses this function must be rendered using the
|
|
103
|
-
# `stream_view_containing_react_components` method from the React on Rails Pro gem.
|
|
104
|
-
#
|
|
105
|
-
# Example of an async React component that can benefit from streaming:
|
|
106
|
-
#
|
|
107
|
-
# const AsyncComponent = async () => {
|
|
108
|
-
# const data = await fetchData();
|
|
109
|
-
# return <div>{data}</div>;
|
|
110
|
-
# };
|
|
111
|
-
#
|
|
112
|
-
# function App() {
|
|
113
|
-
# return (
|
|
114
|
-
# <Suspense fallback={<div>Loading...</div>}>
|
|
115
|
-
# <AsyncComponent />
|
|
116
|
-
# </Suspense>
|
|
117
|
-
# );
|
|
118
|
-
# }
|
|
119
|
-
#
|
|
120
|
-
# @param [String] component_name Name of your registered component
|
|
121
|
-
# @param [Hash] options Options for rendering
|
|
122
|
-
# @option options [Hash] :props Props to pass to the react component
|
|
123
|
-
# @option options [String] :dom_id DOM ID of the component container
|
|
124
|
-
# @option options [Hash] :html_options Options passed to content_tag
|
|
125
|
-
# @option options [Boolean] :trace Set to true to add extra debugging information to the HTML
|
|
126
|
-
# @option options [Boolean] :raise_on_prerender_error Set to true to raise exceptions during server-side rendering
|
|
127
|
-
# Any other options are passed to the content tag, including the id.
|
|
128
|
-
def stream_react_component(component_name, options = {})
|
|
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
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
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)
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
|
|
195
98
|
# react_component_hash is used to return multiple HTML strings for server rendering, such as for
|
|
196
99
|
# adding meta-tags to a page.
|
|
197
100
|
# It is exactly like react_component except for the following:
|
|
@@ -252,10 +155,10 @@ module ReactOnRails
|
|
|
252
155
|
# props: Ruby Hash or JSON string which contains the properties to pass to the redux store.
|
|
253
156
|
# Options
|
|
254
157
|
# defer: false -- pass as true if you wish to render this below your component.
|
|
255
|
-
# immediate_hydration:
|
|
256
|
-
#
|
|
158
|
+
# immediate_hydration: nil -- React on Rails Pro (licensed) feature. When nil (default), Pro users
|
|
159
|
+
# get immediate hydration, non-Pro users don't. Can be explicitly overridden.
|
|
257
160
|
def redux_store(store_name, props: {}, defer: false, immediate_hydration: nil)
|
|
258
|
-
immediate_hydration = ReactOnRails.
|
|
161
|
+
immediate_hydration = ReactOnRails::Utils.normalize_immediate_hydration(immediate_hydration, store_name, "Store")
|
|
259
162
|
|
|
260
163
|
redux_store_data = { store_name: store_name,
|
|
261
164
|
props: props,
|
|
@@ -323,7 +226,7 @@ module ReactOnRails
|
|
|
323
226
|
}
|
|
324
227
|
}
|
|
325
228
|
|
|
326
|
-
consoleReplayScript = ReactOnRails.
|
|
229
|
+
consoleReplayScript = ReactOnRails.getConsoleReplayScript();
|
|
327
230
|
|
|
328
231
|
return JSON.stringify({
|
|
329
232
|
html: htmlResult,
|
|
@@ -339,8 +242,9 @@ module ReactOnRails
|
|
|
339
242
|
.server_render_js_with_console_logging(js_code, render_options)
|
|
340
243
|
|
|
341
244
|
html = result["html"]
|
|
342
|
-
|
|
343
|
-
|
|
245
|
+
console_script = result["consoleReplayScript"]
|
|
246
|
+
console_script_tag = wrap_console_script_with_nonce(console_script) if render_options.replay_console
|
|
247
|
+
raw("#{html}#{console_script_tag}")
|
|
344
248
|
rescue ExecJS::ProgramError => err
|
|
345
249
|
raise ReactOnRails::PrerenderError.new(component_name: "N/A (server_render_js called)",
|
|
346
250
|
err: err,
|
|
@@ -366,7 +270,7 @@ module ReactOnRails
|
|
|
366
270
|
#
|
|
367
271
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
368
272
|
def rails_context(server_side: true)
|
|
369
|
-
# ALERT: Keep in sync with
|
|
273
|
+
# ALERT: Keep in sync with packages/react-on-rails/src/types/index.ts for the properties of RailsContext
|
|
370
274
|
@rails_context ||= begin
|
|
371
275
|
result = {
|
|
372
276
|
componentRegistryTimeout: ReactOnRails.configuration.component_registry_timeout,
|
|
@@ -446,32 +350,6 @@ module ReactOnRails
|
|
|
446
350
|
|
|
447
351
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
448
352
|
|
|
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
353
|
def registered_stores
|
|
476
354
|
@registered_stores ||= []
|
|
477
355
|
end
|
|
@@ -494,25 +372,6 @@ module ReactOnRails
|
|
|
494
372
|
options: options)
|
|
495
373
|
end
|
|
496
374
|
|
|
497
|
-
def internal_stream_react_component(component_name, options = {})
|
|
498
|
-
options = options.merge(render_mode: :html_streaming)
|
|
499
|
-
result = internal_react_component(component_name, options)
|
|
500
|
-
build_react_component_result_for_server_streamed_content(
|
|
501
|
-
rendered_html_stream: result[:result],
|
|
502
|
-
component_specification_tag: result[:tag],
|
|
503
|
-
render_options: result[:render_options]
|
|
504
|
-
)
|
|
505
|
-
end
|
|
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
|
-
|
|
516
375
|
def generated_components_pack_path(component_name)
|
|
517
376
|
"#{ReactOnRails::PackerUtils.packer_source_entry_path}/generated/#{component_name}.js"
|
|
518
377
|
end
|
|
@@ -536,7 +395,7 @@ module ReactOnRails
|
|
|
536
395
|
server_rendered_html.html_safe,
|
|
537
396
|
content_tag_options)
|
|
538
397
|
|
|
539
|
-
result_console_script = render_options.replay_console ? console_script : ""
|
|
398
|
+
result_console_script = render_options.replay_console ? wrap_console_script_with_nonce(console_script) : ""
|
|
540
399
|
result = compose_react_component_html_with_spec_and_console(
|
|
541
400
|
component_specification_tag, rendered_output, result_console_script
|
|
542
401
|
)
|
|
@@ -544,32 +403,6 @@ module ReactOnRails
|
|
|
544
403
|
prepend_render_rails_context(result)
|
|
545
404
|
end
|
|
546
405
|
|
|
547
|
-
def build_react_component_result_for_server_streamed_content(
|
|
548
|
-
rendered_html_stream:,
|
|
549
|
-
component_specification_tag:,
|
|
550
|
-
render_options:
|
|
551
|
-
)
|
|
552
|
-
is_first_chunk = true
|
|
553
|
-
rendered_html_stream.transform do |chunk_json_result|
|
|
554
|
-
if is_first_chunk
|
|
555
|
-
is_first_chunk = false
|
|
556
|
-
build_react_component_result_for_server_rendered_string(
|
|
557
|
-
server_rendered_html: chunk_json_result["html"],
|
|
558
|
-
component_specification_tag: component_specification_tag,
|
|
559
|
-
console_script: chunk_json_result["consoleReplayScript"],
|
|
560
|
-
render_options: render_options
|
|
561
|
-
)
|
|
562
|
-
else
|
|
563
|
-
result_console_script = render_options.replay_console ? chunk_json_result["consoleReplayScript"] : ""
|
|
564
|
-
# No need to prepend component_specification_tag or add rails context again
|
|
565
|
-
# as they're already included in the first chunk
|
|
566
|
-
compose_react_component_html_with_spec_and_console(
|
|
567
|
-
"", chunk_json_result["html"], result_console_script
|
|
568
|
-
)
|
|
569
|
-
end
|
|
570
|
-
end
|
|
571
|
-
end
|
|
572
|
-
|
|
573
406
|
def build_react_component_result_for_server_rendered_hash(
|
|
574
407
|
server_rendered_html: required("server_rendered_html"),
|
|
575
408
|
component_specification_tag: required("component_specification_tag"),
|
|
@@ -587,7 +420,7 @@ module ReactOnRails
|
|
|
587
420
|
server_rendered_html[COMPONENT_HTML_KEY].html_safe,
|
|
588
421
|
content_tag_options)
|
|
589
422
|
|
|
590
|
-
result_console_script = render_options.replay_console ? console_script : ""
|
|
423
|
+
result_console_script = render_options.replay_console ? wrap_console_script_with_nonce(console_script) : ""
|
|
591
424
|
result = compose_react_component_html_with_spec_and_console(
|
|
592
425
|
component_specification_tag, rendered_output, result_console_script
|
|
593
426
|
)
|
|
@@ -604,6 +437,32 @@ module ReactOnRails
|
|
|
604
437
|
)
|
|
605
438
|
end
|
|
606
439
|
|
|
440
|
+
# Wraps console replay JavaScript code in a script tag with CSP nonce if available.
|
|
441
|
+
# The console_script_code is already sanitized by scriptSanitizedVal() in the JavaScript layer,
|
|
442
|
+
# so using html_safe here is secure.
|
|
443
|
+
def wrap_console_script_with_nonce(console_script_code)
|
|
444
|
+
return "" if console_script_code.blank?
|
|
445
|
+
|
|
446
|
+
# Get the CSP nonce if available (Rails 5.2+)
|
|
447
|
+
# Rails 5.2-6.0 use content_security_policy_nonce with no arguments
|
|
448
|
+
# Rails 6.1+ accept an optional directive argument
|
|
449
|
+
nonce = if respond_to?(:content_security_policy_nonce)
|
|
450
|
+
begin
|
|
451
|
+
content_security_policy_nonce(:script)
|
|
452
|
+
rescue ArgumentError
|
|
453
|
+
# Fallback for Rails versions that don't accept arguments
|
|
454
|
+
content_security_policy_nonce
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# Build the script tag with nonce if available
|
|
459
|
+
script_options = { id: "consoleReplayLog" }
|
|
460
|
+
script_options[:nonce] = nonce if nonce.present?
|
|
461
|
+
|
|
462
|
+
# Safe to use html_safe because content is pre-sanitized via scriptSanitizedVal()
|
|
463
|
+
content_tag(:script, console_script_code.html_safe, script_options)
|
|
464
|
+
end
|
|
465
|
+
|
|
607
466
|
def compose_react_component_html_with_spec_and_console(component_specification_tag, rendered_output,
|
|
608
467
|
console_script)
|
|
609
468
|
# IMPORTANT: Ensure that we mark string as html_safe to avoid escaping.
|
|
@@ -620,10 +479,23 @@ module ReactOnRails
|
|
|
620
479
|
|
|
621
480
|
@rendered_rails_context = true
|
|
622
481
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
482
|
+
attribution_comment = react_on_rails_attribution_comment
|
|
483
|
+
script_tag = content_tag(:script,
|
|
484
|
+
json_safe_and_pretty(data).html_safe,
|
|
485
|
+
type: "application/json",
|
|
486
|
+
id: "js-react-on-rails-context")
|
|
487
|
+
|
|
488
|
+
"#{attribution_comment}\n#{script_tag}".html_safe
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# Generates the HTML attribution comment
|
|
492
|
+
# Pro version calls ReactOnRailsPro::Utils for license-specific details
|
|
493
|
+
def react_on_rails_attribution_comment
|
|
494
|
+
if ReactOnRails::Utils.react_on_rails_pro?
|
|
495
|
+
ReactOnRailsPro::Utils.pro_attribution_comment
|
|
496
|
+
else
|
|
497
|
+
"<!-- Powered by React on Rails (c) ShakaCode | Open Source -->"
|
|
498
|
+
end
|
|
627
499
|
end
|
|
628
500
|
|
|
629
501
|
# prepend the rails_context if not yet applied
|
|
@@ -742,6 +614,15 @@ module ReactOnRails
|
|
|
742
614
|
# It doesn't make any transformation, it listens and raises error if a chunk has errors
|
|
743
615
|
chunk_json_result
|
|
744
616
|
end
|
|
617
|
+
|
|
618
|
+
result.rescue do |err|
|
|
619
|
+
# This error came from the renderer
|
|
620
|
+
raise ReactOnRails::PrerenderError.new(component_name: react_component_name,
|
|
621
|
+
# Sanitize as this might be browser logged
|
|
622
|
+
props: sanitized_props_string(props),
|
|
623
|
+
err: err,
|
|
624
|
+
js_code: js_code)
|
|
625
|
+
end
|
|
745
626
|
elsif result["hasErrors"] && render_options.raise_on_prerender_error
|
|
746
627
|
raise_prerender_error(result, react_component_name, props, js_code)
|
|
747
628
|
end
|
|
@@ -794,14 +675,11 @@ module ReactOnRails
|
|
|
794
675
|
end
|
|
795
676
|
|
|
796
677
|
def raise_missing_autoloaded_bundle(react_component_name)
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
MSG
|
|
803
|
-
|
|
804
|
-
raise ReactOnRails::Error, msg
|
|
678
|
+
raise ReactOnRails::SmartError.new(
|
|
679
|
+
error_type: :missing_auto_loaded_bundle,
|
|
680
|
+
component_name: react_component_name,
|
|
681
|
+
expected_path: generated_components_pack_path(react_component_name)
|
|
682
|
+
)
|
|
805
683
|
end
|
|
806
684
|
end
|
|
807
685
|
end
|
|
@@ -4,7 +4,7 @@ require "erb"
|
|
|
4
4
|
|
|
5
5
|
module ReactOnRails
|
|
6
6
|
module Locales
|
|
7
|
-
def self.compile
|
|
7
|
+
def self.compile(force: false)
|
|
8
8
|
config = ReactOnRails.configuration
|
|
9
9
|
check_config_directory_exists(
|
|
10
10
|
directory: config.i18n_dir, key_name: "config.i18n_dir",
|
|
@@ -15,9 +15,9 @@ module ReactOnRails
|
|
|
15
15
|
remove_if: "not using this i18n with React on Rails, or if you want to use all translation files"
|
|
16
16
|
)
|
|
17
17
|
if config.i18n_output_format&.downcase == "js"
|
|
18
|
-
ReactOnRails::Locales::ToJs.new
|
|
18
|
+
ReactOnRails::Locales::ToJs.new(force: force)
|
|
19
19
|
else
|
|
20
|
-
ReactOnRails::Locales::ToJson.new
|
|
20
|
+
ReactOnRails::Locales::ToJson.new(force: force)
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
|
|
@@ -36,12 +36,23 @@ module ReactOnRails
|
|
|
36
36
|
private_class_method :check_config_directory_exists
|
|
37
37
|
|
|
38
38
|
class Base
|
|
39
|
-
def initialize
|
|
39
|
+
def initialize(force: false)
|
|
40
40
|
return if i18n_dir.nil?
|
|
41
|
-
|
|
41
|
+
|
|
42
|
+
if locale_files.empty?
|
|
43
|
+
puts "Warning: No locale files found in #{i18n_yml_dir || 'Rails i18n load path'}"
|
|
44
|
+
return
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if !force && !obsolete?
|
|
48
|
+
puts "Locale files are up to date, skipping generation. " \
|
|
49
|
+
"Use 'rake react_on_rails:locale force=true' to force regeneration."
|
|
50
|
+
return
|
|
51
|
+
end
|
|
42
52
|
|
|
43
53
|
@translations, @defaults = generate_translations
|
|
44
54
|
convert
|
|
55
|
+
puts "Generated locale files in #{i18n_dir}"
|
|
45
56
|
end
|
|
46
57
|
|
|
47
58
|
private
|
|
@@ -49,6 +60,7 @@ module ReactOnRails
|
|
|
49
60
|
def file_format; end
|
|
50
61
|
|
|
51
62
|
def obsolete?
|
|
63
|
+
return true if exist_files.length != files.length # Some files missing
|
|
52
64
|
return true if exist_files.empty?
|
|
53
65
|
|
|
54
66
|
files_are_outdated
|
|
@@ -55,8 +55,12 @@ module ReactOnRails
|
|
|
55
55
|
# the webpack-dev-server is provided by the config value
|
|
56
56
|
# "same_bundle_for_client_and_server" where a value of true
|
|
57
57
|
# would mean that the bundle is created by the webpack-dev-server
|
|
58
|
-
is_bundle_running_on_server =
|
|
59
|
-
|
|
58
|
+
is_bundle_running_on_server = bundle_name == ReactOnRails.configuration.server_bundle_js_file
|
|
59
|
+
|
|
60
|
+
# Check Pro RSC bundle if Pro is available
|
|
61
|
+
if ReactOnRails::Utils.react_on_rails_pro?
|
|
62
|
+
is_bundle_running_on_server ||= (bundle_name == ReactOnRailsPro.configuration.rsc_bundle_js_file)
|
|
63
|
+
end
|
|
60
64
|
|
|
61
65
|
if ::Shakapacker.dev_server.running? && (!is_bundle_running_on_server ||
|
|
62
66
|
ReactOnRails.configuration.same_bundle_for_client_and_server)
|
|
@@ -162,5 +166,78 @@ module ReactOnRails
|
|
|
162
166
|
|
|
163
167
|
raise ReactOnRails::Error, msg
|
|
164
168
|
end
|
|
169
|
+
|
|
170
|
+
# Check if shakapacker.yml has a precompile hook configured
|
|
171
|
+
# This prevents react_on_rails from running generate_packs twice
|
|
172
|
+
#
|
|
173
|
+
# Returns false if detection fails for any reason (missing shakapacker, malformed config, etc.)
|
|
174
|
+
# to ensure generate_packs runs rather than being incorrectly skipped
|
|
175
|
+
#
|
|
176
|
+
# Note: Currently checks a single hook value. Future enhancement will support hook lists
|
|
177
|
+
# to allow prepending/appending multiple commands. See related Shakapacker issue for details.
|
|
178
|
+
def self.shakapacker_precompile_hook_configured?
|
|
179
|
+
return false unless defined?(::Shakapacker)
|
|
180
|
+
|
|
181
|
+
hook_value = extract_precompile_hook
|
|
182
|
+
return false if hook_value.nil?
|
|
183
|
+
|
|
184
|
+
hook_contains_generate_packs?(hook_value)
|
|
185
|
+
rescue StandardError => e
|
|
186
|
+
# Swallow errors during hook detection to fail safe - if we can't detect the hook,
|
|
187
|
+
# we should run generate_packs rather than skip it incorrectly.
|
|
188
|
+
# Possible errors: NoMethodError (config method missing), TypeError (unexpected data structure),
|
|
189
|
+
# or errors from shakapacker's internal implementation changes
|
|
190
|
+
warn "Warning: Unable to detect shakapacker precompile hook: #{e.message}" if ENV["DEBUG"]
|
|
191
|
+
false
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def self.extract_precompile_hook
|
|
195
|
+
# Access config data using private :data method since there's no public API
|
|
196
|
+
# to access the raw configuration hash needed for hook detection
|
|
197
|
+
config_data = ::Shakapacker.config.send(:data)
|
|
198
|
+
|
|
199
|
+
# Try symbol keys first (Shakapacker's internal format), then fall back to string keys
|
|
200
|
+
# The key is 'precompile_hook' at the top level of the config
|
|
201
|
+
config_data&.[](:precompile_hook) || config_data&.[]("precompile_hook")
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def self.hook_contains_generate_packs?(hook_value)
|
|
205
|
+
# The hook value can be either:
|
|
206
|
+
# 1. A direct command containing the rake task
|
|
207
|
+
# 2. A path to a script file that needs to be read
|
|
208
|
+
return false if hook_value.blank?
|
|
209
|
+
|
|
210
|
+
# Check if it's a direct command first
|
|
211
|
+
return true if hook_value.to_s.match?(/\breact_on_rails:generate_packs\b/)
|
|
212
|
+
|
|
213
|
+
# Check if it's a script file path
|
|
214
|
+
script_path = resolve_hook_script_path(hook_value)
|
|
215
|
+
return false unless script_path && File.exist?(script_path)
|
|
216
|
+
|
|
217
|
+
# Read and check script contents
|
|
218
|
+
script_contents = File.read(script_path)
|
|
219
|
+
script_contents.match?(/\breact_on_rails:generate_packs\b/)
|
|
220
|
+
rescue StandardError
|
|
221
|
+
# If we can't read the script, assume it doesn't contain generate_packs
|
|
222
|
+
false
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def self.resolve_hook_script_path(hook_value)
|
|
226
|
+
# Hook value might be a script path relative to Rails root
|
|
227
|
+
return nil unless defined?(Rails) && Rails.respond_to?(:root)
|
|
228
|
+
|
|
229
|
+
potential_path = Rails.root.join(hook_value.to_s.strip)
|
|
230
|
+
potential_path if potential_path.file?
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Returns the configured precompile hook value for logging/debugging
|
|
234
|
+
# Returns nil if no hook is configured
|
|
235
|
+
def self.shakapacker_precompile_hook_value
|
|
236
|
+
return nil unless defined?(::Shakapacker)
|
|
237
|
+
|
|
238
|
+
extract_precompile_hook
|
|
239
|
+
rescue StandardError
|
|
240
|
+
nil
|
|
241
|
+
end
|
|
165
242
|
end
|
|
166
243
|
end
|