react_on_rails 16.7.0.rc.3 → 17.0.0.rc.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/Gemfile.development_dependencies +7 -3
- data/Gemfile.lock +8 -8
- data/lib/generators/react_on_rails/js_dependency_manager.rb +7 -0
- data/lib/generators/react_on_rails/pro_setup.rb +56 -4
- data/lib/generators/react_on_rails/rsc_setup/client_references.rb +82 -28
- data/lib/generators/react_on_rails/templates/pro/base/config/initializers/react_on_rails_pro.rb.tt +6 -1
- data/lib/generators/react_on_rails/templates/pro/base/renderer/{node-renderer.js → node-renderer.js.tt} +6 -1
- data/lib/react_on_rails/doctor.rb +19 -9
- data/lib/react_on_rails/engine.rb +2 -1
- data/lib/react_on_rails/helper.rb +35 -1
- data/lib/react_on_rails/prerender_error.rb +14 -6
- data/lib/react_on_rails/test_helper/webpack_assets_compiler.rb +4 -4
- data/lib/react_on_rails/version.rb +1 -1
- data/rakelib/lint.rake +1 -1
- data/rakelib/run_rspec.rake +0 -2
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 02f0fa1befbed99b17f624249398f47b5a1395ed5407079a472ad39218d2cfc1
|
|
4
|
+
data.tar.gz: f8b4e5a3abf309b98f7b244d1a807acb29096ff7ae1ea115e780eefd35f2d609
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c594211d78c05817226dd5d4ff06e7bb4f377b57320cdb824de7a8902ec40d66473b28da64e0f6d2df5b445786a050d1c6b61779af3fea0c1c4793f3955efa63
|
|
7
|
+
data.tar.gz: e86d35866011de451f725c441539bc7f8400e83d9702a4a3b9058d385e483a926229b4e33e18685636d73700bc7a9a1106246d78174a726327b47b8b0f1645d9
|
|
@@ -2,18 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
eval_gemfile File.expand_path("../Gemfile.shared_dev_dependencies", __dir__)
|
|
4
4
|
|
|
5
|
-
gem "shakapacker", "
|
|
5
|
+
gem "shakapacker", "10.1.0"
|
|
6
6
|
gem "bootsnap", require: false
|
|
7
7
|
gem "rails", "~> 7.1.0"
|
|
8
8
|
|
|
9
|
-
gem "sqlite3", "~>
|
|
9
|
+
gem "sqlite3", "~> 2.0"
|
|
10
10
|
gem "sass-rails", "~> 6.0"
|
|
11
11
|
gem "uglifier"
|
|
12
12
|
gem "jquery-rails"
|
|
13
13
|
gem "puma", "~> 6.0"
|
|
14
14
|
|
|
15
15
|
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
|
|
16
|
-
|
|
16
|
+
# Declared unconditionally so the gemset always matches Gemfile.lock. DISABLE_TURBOLINKS
|
|
17
|
+
# toggles Turbolinks at the application layer (the require_asset in
|
|
18
|
+
# spec/dummy/app/assets/javascripts/application_non_webpack.js.erb), not gem inclusion.
|
|
19
|
+
# A conditional declaration broke `bundle install --frozen` in CI (see AGENTS.md).
|
|
20
|
+
gem "turbolinks"
|
|
17
21
|
|
|
18
22
|
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
|
|
19
23
|
gem "jbuilder"
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
react_on_rails (
|
|
4
|
+
react_on_rails (17.0.0.rc.0)
|
|
5
5
|
addressable
|
|
6
6
|
connection_pool
|
|
7
7
|
execjs (~> 2.5)
|
|
@@ -237,7 +237,7 @@ GEM
|
|
|
237
237
|
nio4r (~> 2.0)
|
|
238
238
|
racc (1.8.1)
|
|
239
239
|
rack (3.2.5)
|
|
240
|
-
rack-proxy (0.
|
|
240
|
+
rack-proxy (0.8.2)
|
|
241
241
|
rack
|
|
242
242
|
rack-session (2.1.1)
|
|
243
243
|
base64 (>= 0.1.0)
|
|
@@ -360,7 +360,7 @@ GEM
|
|
|
360
360
|
rubyzip (>= 1.2.2, < 3.0)
|
|
361
361
|
websocket (~> 1.0)
|
|
362
362
|
semantic_range (3.1.1)
|
|
363
|
-
shakapacker (
|
|
363
|
+
shakapacker (10.1.0)
|
|
364
364
|
activesupport (>= 5.2)
|
|
365
365
|
package_json
|
|
366
366
|
rack-proxy (>= 0.6.1)
|
|
@@ -379,10 +379,10 @@ GEM
|
|
|
379
379
|
actionpack (>= 5.2)
|
|
380
380
|
activesupport (>= 5.2)
|
|
381
381
|
sprockets (>= 3.0.0)
|
|
382
|
-
sqlite3 (
|
|
382
|
+
sqlite3 (2.9.4)
|
|
383
383
|
mini_portile2 (~> 2.8.0)
|
|
384
|
-
sqlite3 (
|
|
385
|
-
sqlite3 (
|
|
384
|
+
sqlite3 (2.9.4-arm64-darwin)
|
|
385
|
+
sqlite3 (2.9.4-x86_64-linux-gnu)
|
|
386
386
|
steep (1.9.4)
|
|
387
387
|
activesupport (>= 5.1)
|
|
388
388
|
concurrent-ruby (>= 1.1.10)
|
|
@@ -475,11 +475,11 @@ DEPENDENCIES
|
|
|
475
475
|
sass-rails (~> 6.0)
|
|
476
476
|
sdoc
|
|
477
477
|
selenium-webdriver (= 4.9.0)
|
|
478
|
-
shakapacker (=
|
|
478
|
+
shakapacker (= 10.1.0)
|
|
479
479
|
simplecov (~> 0.16.1)
|
|
480
480
|
spring (~> 4.0)
|
|
481
481
|
sprockets (~> 4.0)
|
|
482
|
-
sqlite3 (~>
|
|
482
|
+
sqlite3 (~> 2.0)
|
|
483
483
|
steep
|
|
484
484
|
turbo-rails
|
|
485
485
|
turbolinks
|
|
@@ -578,12 +578,19 @@ module ReactOnRails
|
|
|
578
578
|
end
|
|
579
579
|
|
|
580
580
|
File.write("package.json", "#{JSON.pretty_generate(content)}\n")
|
|
581
|
+
GeneratorMessages.add_warning(package_json_pin_fallback_warning(versioned_packages))
|
|
581
582
|
true
|
|
582
583
|
rescue StandardError => e
|
|
583
584
|
GeneratorMessages.add_warning("⚠️ Could not write dependency pins to package.json: #{e.message}")
|
|
584
585
|
false
|
|
585
586
|
end
|
|
586
587
|
|
|
588
|
+
def package_json_pin_fallback_warning(versioned_packages)
|
|
589
|
+
pinned_list = versioned_packages.map { |name, version| "#{name}@#{version}" }.join(", ")
|
|
590
|
+
"⚠️ Package manager install failed. Wrote the following version pins to package.json " \
|
|
591
|
+
"so you can rerun your package manager manually: #{pinned_list}"
|
|
592
|
+
end
|
|
593
|
+
|
|
587
594
|
def fallback_package_manager
|
|
588
595
|
package_manager = GeneratorMessages.detect_package_manager(app_root: destination_root)
|
|
589
596
|
return package_manager if GeneratorMessages.supported_package_manager?(package_manager)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "securerandom"
|
|
3
4
|
require_relative "generator_messages"
|
|
4
5
|
require "react_on_rails/node_renderer_procfile"
|
|
5
6
|
require "react_on_rails/pro_migration"
|
|
@@ -46,16 +47,58 @@ module ReactOnRails
|
|
|
46
47
|
say set_color("🚀 REACT ON RAILS PRO SETUP", :cyan, :bold)
|
|
47
48
|
say set_color("=" * 80, :cyan)
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
# The Rails initializer and Node renderer bootstrap must share the same
|
|
51
|
+
# password literal. Only mint a fresh random password when BOTH files will
|
|
52
|
+
# be created — otherwise nil so each template falls back to the env-only
|
|
53
|
+
# branch, avoiding a literal mismatch with any existing file.
|
|
54
|
+
# Always reassign so a stale value from a prior invocation on the same
|
|
55
|
+
# instance can't leak into a later partial-install run.
|
|
56
|
+
@generated_renderer_password = nil
|
|
57
|
+
if pro_initializer_will_be_created? && node_renderer_will_be_created?
|
|
58
|
+
@generated_renderer_password = SecureRandom.hex(32)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
initializer_created = create_pro_initializer
|
|
50
62
|
legacy_renderer_detected = create_node_renderer
|
|
51
63
|
add_pro_to_procfiles unless legacy_renderer_detected
|
|
52
64
|
update_webpack_config_for_pro
|
|
53
65
|
|
|
66
|
+
say_renderer_password_setup_summary(initializer_created)
|
|
67
|
+
|
|
54
68
|
say set_color("=" * 80, :cyan)
|
|
55
69
|
say "✅ React on Rails Pro setup complete!", :green
|
|
56
70
|
say set_color("=" * 80, :cyan)
|
|
57
71
|
end
|
|
58
72
|
|
|
73
|
+
def say_renderer_password_setup_summary(initializer_created)
|
|
74
|
+
if @generated_renderer_password
|
|
75
|
+
say ""
|
|
76
|
+
say set_color("🔐 A random renderer password was written into your config files.", :yellow, :bold)
|
|
77
|
+
say " For production, set RENDERER_PASSWORD as an env var instead and"
|
|
78
|
+
say " remove the literal value from version control."
|
|
79
|
+
say " See: https://www.shakacode.com/react-on-rails/docs/pro/node-renderer/"
|
|
80
|
+
say ""
|
|
81
|
+
elsif initializer_created
|
|
82
|
+
# Initializer was newly created but the Node renderer file already exists;
|
|
83
|
+
# the new initializer falls back to ENV["RENDERER_PASSWORD"] only so it doesn't
|
|
84
|
+
# disagree with whatever literal the existing renderer file contains.
|
|
85
|
+
say ""
|
|
86
|
+
say set_color("⚠️ Existing Node renderer detected — Rails initializer uses " \
|
|
87
|
+
"ENV[\"RENDERER_PASSWORD\"] only.", :yellow, :bold)
|
|
88
|
+
say " Set RENDERER_PASSWORD in your environment to match the password in your existing renderer."
|
|
89
|
+
say ""
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def pro_initializer_will_be_created?
|
|
94
|
+
!File.exist?(File.join(destination_root, "config/initializers/react_on_rails_pro.rb"))
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def node_renderer_will_be_created?
|
|
98
|
+
!File.exist?(File.join(destination_root, "renderer/node-renderer.js")) &&
|
|
99
|
+
!File.exist?(File.join(destination_root, "client/node-renderer.js"))
|
|
100
|
+
end
|
|
101
|
+
|
|
59
102
|
# Check if the Pro gem is missing. When the base react_on_rails gem is in
|
|
60
103
|
# the Gemfile, installation is deferred to the later Gemfile swap (which
|
|
61
104
|
# preserves the user's version pin); otherwise auto-install via `bundle
|
|
@@ -205,14 +248,18 @@ module ReactOnRails
|
|
|
205
248
|
|
|
206
249
|
if File.exist?(File.join(destination_root, initializer_path))
|
|
207
250
|
say "ℹ️ #{initializer_path} already exists, skipping", :yellow
|
|
208
|
-
return
|
|
251
|
+
return false
|
|
209
252
|
end
|
|
210
253
|
|
|
211
254
|
say "📝 Creating React on Rails Pro initializer...", :yellow
|
|
212
255
|
|
|
256
|
+
# @generated_renderer_password is set by setup_pro only when both this
|
|
257
|
+
# file and the Node renderer bootstrap will be created together; nil here
|
|
258
|
+
# means the template emits the env-only fallback (no literal password).
|
|
213
259
|
template("templates/pro/base/config/initializers/react_on_rails_pro.rb.tt", initializer_path)
|
|
214
260
|
|
|
215
261
|
say "✅ Created #{initializer_path}", :green
|
|
262
|
+
true
|
|
216
263
|
end
|
|
217
264
|
|
|
218
265
|
# Matches active (uncommented) Procfile.dev node-renderer lines that
|
|
@@ -267,6 +314,11 @@ module ReactOnRails
|
|
|
267
314
|
"to migrate, move it to #{node_renderer_path} and update any references " \
|
|
268
315
|
"(e.g. Procfile.dev, Procfile.prod, Docker CMD / command):", :yellow
|
|
269
316
|
say " #{node_renderer_procfile_command('Procfile.dev')}", :yellow
|
|
317
|
+
say set_color(
|
|
318
|
+
"⚠️ Ensure the password in #{legacy_node_renderer_path} matches " \
|
|
319
|
+
"config/initializers/react_on_rails_pro.rb. Both must use the same RENDERER_PASSWORD.",
|
|
320
|
+
:yellow
|
|
321
|
+
)
|
|
270
322
|
warn_on_stale_legacy_procfile_entry
|
|
271
323
|
return true
|
|
272
324
|
end
|
|
@@ -275,8 +327,8 @@ module ReactOnRails
|
|
|
275
327
|
|
|
276
328
|
empty_directory("renderer")
|
|
277
329
|
|
|
278
|
-
template_path = "templates/pro/base/renderer/node-renderer.js"
|
|
279
|
-
|
|
330
|
+
template_path = "templates/pro/base/renderer/node-renderer.js.tt"
|
|
331
|
+
template(template_path, node_renderer_path)
|
|
280
332
|
|
|
281
333
|
say "✅ Created #{node_renderer_path}", :green
|
|
282
334
|
false
|
|
@@ -123,7 +123,7 @@ module ReactOnRails
|
|
|
123
123
|
def update_existing_rsc_webpack_config(config_path, content, is_server:)
|
|
124
124
|
return unless rsc_plugin_sections_safe_to_rewrite?(config_path, content, is_server: is_server)
|
|
125
125
|
return if rsc_plugin_uses_scoped_client_references?(content, is_server: is_server)
|
|
126
|
-
return unless
|
|
126
|
+
return unless prepare_rsc_client_references_rewrite!(config_path, content, is_server: is_server)
|
|
127
127
|
|
|
128
128
|
return if rewrite_rsc_plugin_client_references(config_path, is_server: is_server)
|
|
129
129
|
|
|
@@ -131,25 +131,31 @@ module ReactOnRails
|
|
|
131
131
|
warn_missing_rsc_plugin_target(config_path, is_server: is_server)
|
|
132
132
|
end
|
|
133
133
|
|
|
134
|
-
def
|
|
135
|
-
# This
|
|
136
|
-
# may already have been injected on disk so
|
|
137
|
-
# can re-read fresh content with valid offsets.
|
|
134
|
+
def prepare_rsc_client_references_rewrite!(config_path, content, is_server:)
|
|
135
|
+
# This prepares the rewrite and returns whether it should continue. When it returns true,
|
|
136
|
+
# the scoped helper may already have been injected on disk so
|
|
137
|
+
# `rewrite_rsc_plugin_client_references` can re-read fresh content with valid offsets.
|
|
138
138
|
if rsc_plugin_references_any_scoped_client_references?(content, is_server: is_server)
|
|
139
139
|
return false unless ensure_rsc_client_references_setup(config_path, content, is_server: is_server)
|
|
140
140
|
|
|
141
|
-
return
|
|
141
|
+
return rsc_plugin_needs_client_references_rewrite?(content, is_server: is_server)
|
|
142
142
|
end
|
|
143
143
|
|
|
144
144
|
rewritable_rsc_plugin?(config_path, content, is_server: is_server) &&
|
|
145
145
|
ensure_rsc_client_references_setup(config_path, content, is_server: is_server)
|
|
146
146
|
end
|
|
147
147
|
|
|
148
|
+
def rsc_plugin_needs_client_references_rewrite?(content, is_server:)
|
|
149
|
+
any_rsc_plugin_section_without_client_references?(content, is_server: is_server)
|
|
150
|
+
end
|
|
151
|
+
|
|
148
152
|
# Detects RSCWebpackPlugin option blocks that the lightweight JS scanner could not parse
|
|
149
153
|
# cleanly (most often a regex literal with an unmatched `{` / `}` that walks the depth
|
|
150
154
|
# counter past the real closing brace). When found, we warn and refuse to rewrite anything
|
|
151
155
|
# in the file so a sibling rewrite cannot accidentally splice into a wrong location.
|
|
152
156
|
def rsc_plugin_sections_safe_to_rewrite?(config_path, content, is_server:)
|
|
157
|
+
# `:unparseable` is file-wide: the partitioner increments it before filtering parseable
|
|
158
|
+
# sections by the current `is_server` target.
|
|
153
159
|
unparseable = rsc_plugin_option_sections_partition(content, is_server: is_server).fetch(:unparseable)
|
|
154
160
|
return true if unparseable.zero?
|
|
155
161
|
|
|
@@ -160,7 +166,7 @@ module ReactOnRails
|
|
|
160
166
|
def rewritable_rsc_plugin?(config_path, content, is_server:)
|
|
161
167
|
# Mixed same-target plugins are still rewritable: the later rewrite only updates plugins
|
|
162
168
|
# missing clientReferences and leaves sibling custom clientReferences untouched.
|
|
163
|
-
return true if
|
|
169
|
+
return true if any_rsc_plugin_section_without_client_references?(content, is_server: is_server)
|
|
164
170
|
|
|
165
171
|
if rsc_plugin_defines_client_references?(content, is_server: is_server)
|
|
166
172
|
GeneratorMessages.add_warning(
|
|
@@ -258,18 +264,24 @@ module ReactOnRails
|
|
|
258
264
|
end
|
|
259
265
|
end
|
|
260
266
|
|
|
261
|
-
|
|
267
|
+
# Existential check: returns true when at least one matching plugin section is missing a
|
|
268
|
+
# top-level `clientReferences:` key. Pairs with `rsc_plugin_defines_client_references?`,
|
|
269
|
+
# which uses the same any-section semantics for the opposite condition. The two are not
|
|
270
|
+
# complements when multiple plugin sections exist — a file with one configured plugin and
|
|
271
|
+
# one unconfigured plugin returns true from both.
|
|
272
|
+
def any_rsc_plugin_section_without_client_references?(content, is_server:)
|
|
262
273
|
rsc_plugin_option_sections(content, is_server: is_server).any? do |section|
|
|
263
274
|
!rsc_plugin_body_has_top_level_key?(section.fetch(:body), "clientReferences")
|
|
264
275
|
end
|
|
265
276
|
end
|
|
266
277
|
|
|
267
278
|
# Strips JavaScript line and block comments while preserving string-literal contents,
|
|
268
|
-
#
|
|
279
|
+
# including simple `${...}` template-literal interpolation, so `clientReferences:` /
|
|
280
|
+
# `isServer:` substrings inside strings are not mis-detected.
|
|
269
281
|
# Shares the `advance_js_scan_state` family used by `js_top_level_position?` and
|
|
270
282
|
# `matching_js_closing_brace` so all JS-aware passes follow the same comment/string rules.
|
|
271
|
-
#
|
|
272
|
-
#
|
|
283
|
+
# See `advance_js_scan_state` for the scanner's supported surface (including the regex-
|
|
284
|
+
# literal and nested-template-literal limits that callers must be aware of).
|
|
273
285
|
def rsc_plugin_options_without_comments(options)
|
|
274
286
|
result = String.new(capacity: options.length)
|
|
275
287
|
state = nil
|
|
@@ -429,15 +441,15 @@ module ReactOnRails
|
|
|
429
441
|
end
|
|
430
442
|
|
|
431
443
|
# Expects `content[open_index] == "{"`; callers pass the options-object opening brace.
|
|
432
|
-
# This lightweight scanner
|
|
433
|
-
#
|
|
434
|
-
#
|
|
435
|
-
#
|
|
436
|
-
#
|
|
437
|
-
#
|
|
438
|
-
#
|
|
439
|
-
#
|
|
440
|
-
#
|
|
444
|
+
# This lightweight scanner supports strings (including template literals for simple
|
|
445
|
+
# `${...}` interpolation) plus JS line/block comments. It does not classify regex
|
|
446
|
+
# literals, so braces inside constructs such as `/a{2}/` or `/[{]/` can be counted as
|
|
447
|
+
# object braces. Nested template literals (for example, `outer ${`inner`}`) are also
|
|
448
|
+
# unsupported: the inner backtick falsely closes the outer string state, exposing later
|
|
449
|
+
# braces to the depth counter. `rsc_plugin_options_without_comments` shares the same
|
|
450
|
+
# supported surface, and callers detect corrupted sections via
|
|
451
|
+
# `rsc_plugin_options_followed_by_close_paren?` so the migration warns instead of
|
|
452
|
+
# producing a corrupt rewrite.
|
|
441
453
|
def matching_js_closing_brace(content, open_index)
|
|
442
454
|
depth = 0
|
|
443
455
|
index = open_index
|
|
@@ -470,8 +482,51 @@ module ReactOnRails
|
|
|
470
482
|
nil
|
|
471
483
|
end
|
|
472
484
|
|
|
473
|
-
#
|
|
474
|
-
#
|
|
485
|
+
# Central dispatcher for the lightweight JS scanner shared by every JS-aware pass in this
|
|
486
|
+
# generator (`matching_js_closing_brace`, `js_top_level_position?`, `js_code_position?`,
|
|
487
|
+
# `rsc_plugin_options_without_comments`, `first_significant_js_index`,
|
|
488
|
+
# `rsc_plugin_options_followed_by_close_paren?`, `last_js_code_char_index`,
|
|
489
|
+
# `last_js_code_line_start`). Return index is the last consumed character. Line comments
|
|
490
|
+
# leave the newline for the caller's normal index increment; block comments consume the
|
|
491
|
+
# closing slash.
|
|
492
|
+
#
|
|
493
|
+
# Supported lexical constructs:
|
|
494
|
+
# - Line comments (`// ...\n`) and block comments (`/* ... */`).
|
|
495
|
+
# - Single-quoted (`'...'`), double-quoted (`"..."`), and template-literal (`` `...` ``)
|
|
496
|
+
# strings, including escape sequences and the simple `${expr}` interpolation form
|
|
497
|
+
# (interpolation braces stay inside the string state and never reach the depth counter).
|
|
498
|
+
#
|
|
499
|
+
# Outside the supported surface for callers that do **not** run `js_regex_literal_start?`
|
|
500
|
+
# preprocessing (e.g. `matching_js_closing_brace`, `rsc_plugin_options_without_comments`),
|
|
501
|
+
# the scanner cannot distinguish these from the syntax they shadow, so `{`/`}` characters
|
|
502
|
+
# they contain can confuse the depth counter. `js_top_level_position?` and
|
|
503
|
+
# `js_code_position?` handle regex literals via their own pre-pass and are unaffected.
|
|
504
|
+
# - Regex literals (e.g. `/a{2}/`, `/\{/`, `/[{]/`): not recognized as a distinct state,
|
|
505
|
+
# so brace-containing patterns walk the depth counter past the real options close. The
|
|
506
|
+
# user-facing warning text in `warn_unparseable_rsc_plugin_sections` calls these out
|
|
507
|
+
# explicitly.
|
|
508
|
+
# - Nested template literals (`` `outer ${`inner`}` ``): the inner backtick falsely closes
|
|
509
|
+
# the outer string state, exposing later braces to the depth counter.
|
|
510
|
+
#
|
|
511
|
+
# The downstream `rsc_plugin_option_sections_partition` catches both failure modes by
|
|
512
|
+
# requiring the matched closing `}` to be followed by `)`. When it isn't, the section is
|
|
513
|
+
# marked unparseable and `warn_unparseable_rsc_plugin_sections` asks the user to add
|
|
514
|
+
# `clientReferences:` manually — the migration declines to rewrite rather than risk
|
|
515
|
+
# corrupting the config.
|
|
516
|
+
#
|
|
517
|
+
# Future expansion (only worth doing if a real-world RSC plugin options block needs it):
|
|
518
|
+
# 1. Add a `:regex_literal` state alongside the string and comment states. Track regex
|
|
519
|
+
# contexts by detecting `/` after a token that legally precedes a regex literal
|
|
520
|
+
# (`=`, `(`, `,`, `:`, `;`, `?`, `!`, `&&`, `||`, `return`, `typeof`, etc.) and consume
|
|
521
|
+
# until the unescaped closing `/` plus any flags. The token-context check is necessary
|
|
522
|
+
# because the same `/` character means division in expression position.
|
|
523
|
+
# 2. Add a stack-based template-literal state so nested `` `...${`inner`}...` `` pairs
|
|
524
|
+
# track depth instead of toggling a single boolean state.
|
|
525
|
+
# Regex literals require expanding `advance_js_default_scan_state`; nested template
|
|
526
|
+
# literals would also require replacing `advance_js_string_state` with stack-aware
|
|
527
|
+
# handling. Both changes need a new state-machine branch; the current callers were
|
|
528
|
+
# specifically designed around the simpler scanner and would need re-validation against
|
|
529
|
+
# the expanded state set.
|
|
475
530
|
#
|
|
476
531
|
# IMPORTANT CALLER CONTRACT — block-comment exit:
|
|
477
532
|
# When this returns from a `*/` exit, `state` is cleared, but `char` is still the `*` and
|
|
@@ -909,13 +964,12 @@ module ReactOnRails
|
|
|
909
964
|
# generator has already confirmed `RSCWebpackPlugin` is imported in the file. That's why
|
|
910
965
|
# this helper deliberately omits the `RSCWebpackPlugin` import that `inject_rsc_*_imports`
|
|
911
966
|
# adds on the from-scratch path — adding it here would produce a duplicate import.
|
|
967
|
+
# Must only be called via `ensure_rsc_client_references_setup`, which has already verified
|
|
968
|
+
# that no module-scope `rscClientReferences` declaration exists and that the import anchor
|
|
969
|
+
# is present and not yet consumed by a previous call. Bypassing that guard can write a
|
|
970
|
+
# duplicate `const rscClientReferences` declaration and leave the user's webpack config with
|
|
971
|
+
# a Node SyntaxError at load time.
|
|
912
972
|
def add_rsc_client_references_setup(config_path, content, existing_imports_content, is_server:)
|
|
913
|
-
# Belt-and-suspenders: the only caller, `ensure_rsc_client_references_setup`, already
|
|
914
|
-
# checks both `scoped_rsc_client_references_defined?` and `rsc_client_references_defined?`
|
|
915
|
-
# before delegating here. The guards are kept so the helper is safe to call directly.
|
|
916
|
-
return false if scoped_rsc_client_references_defined?(content)
|
|
917
|
-
return false if rsc_client_references_defined?(content)
|
|
918
|
-
|
|
919
973
|
replace_rsc_client_references_setup_anchor(config_path, content, is_server: is_server) do |anchor|
|
|
920
974
|
join_rsc_client_references_setup(
|
|
921
975
|
content,
|
data/lib/generators/react_on_rails/templates/pro/base/config/initializers/react_on_rails_pro.rb.tt
CHANGED
|
@@ -7,7 +7,12 @@ ReactOnRailsPro.configure do |config|
|
|
|
7
7
|
config.renderer_url = ENV.fetch("REACT_RENDERER_URL", "http://localhost:3800")
|
|
8
8
|
|
|
9
9
|
# See value in renderer/node-renderer.js
|
|
10
|
-
|
|
10
|
+
# For production, set RENDERER_PASSWORD as an env var and remove the literal fallback.
|
|
11
|
+
<% if @generated_renderer_password -%>
|
|
12
|
+
config.renderer_password = ENV.fetch("RENDERER_PASSWORD", "<%= @generated_renderer_password %>")
|
|
13
|
+
<% else -%>
|
|
14
|
+
config.renderer_password = ENV["RENDERER_PASSWORD"]
|
|
15
|
+
<% end -%>
|
|
11
16
|
|
|
12
17
|
config.ssr_timeout = 5
|
|
13
18
|
config.renderer_request_retry_limit = 1
|
|
@@ -12,7 +12,12 @@ const config = {
|
|
|
12
12
|
logLevel: env.RENDERER_LOG_LEVEL || 'info',
|
|
13
13
|
|
|
14
14
|
// See value in /config/initializers/react_on_rails_pro.rb
|
|
15
|
-
|
|
15
|
+
// For production, set RENDERER_PASSWORD as an env var and remove the literal fallback.
|
|
16
|
+
<% if @generated_renderer_password -%>
|
|
17
|
+
password: env.RENDERER_PASSWORD ?? '<%= @generated_renderer_password %>',
|
|
18
|
+
<% else -%>
|
|
19
|
+
password: env.RENDERER_PASSWORD,
|
|
20
|
+
<% end -%>
|
|
16
21
|
|
|
17
22
|
// Number of Node.js worker threads for SSR rendering
|
|
18
23
|
// Set RENDERER_WORKERS_COUNT env var to override (e.g., for production tuning)
|
|
@@ -94,7 +94,8 @@ module ReactOnRails
|
|
|
94
94
|
"scripts/deploy.sh",
|
|
95
95
|
".circleci/config.yml",
|
|
96
96
|
".gitlab-ci.yml",
|
|
97
|
-
"bitbucket-pipelines.yml"
|
|
97
|
+
"bitbucket-pipelines.yml",
|
|
98
|
+
"Jenkinsfile"
|
|
98
99
|
].freeze
|
|
99
100
|
# Bounded glob allowlist for deploy manifests that live in a known directory
|
|
100
101
|
# but use per-environment or per-workflow filenames. Each pattern matches
|
|
@@ -2898,20 +2899,29 @@ module ReactOnRails
|
|
|
2898
2899
|
checker.add_warning("⚠️ Could not complete scan for deprecated renderer-cache task references: #{e.message}")
|
|
2899
2900
|
end
|
|
2900
2901
|
|
|
2901
|
-
def deploy_script_references_deprecated_task?(full_path)
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
# The trailing-comment strip requires whitespace before
|
|
2905
|
-
# like `task#name`
|
|
2902
|
+
def deploy_script_references_deprecated_task?(full_path, path)
|
|
2903
|
+
comment_prefixes = renderer_cache_deploy_script_comment_prefixes(path)
|
|
2904
|
+
|
|
2905
|
+
# The trailing-comment strip requires whitespace before the comment marker,
|
|
2906
|
+
# so fragments like `task#name` stay intact while `cmd # was: <deprecated>`
|
|
2907
|
+
# and Jenkinsfile `cmd // was: <deprecated>` comments are filtered.
|
|
2906
2908
|
full_path.binread.each_line.any? do |line|
|
|
2907
2909
|
stripped = line.lstrip
|
|
2908
|
-
next false if stripped.start_with?(
|
|
2910
|
+
next false if comment_prefixes.any? { |prefix| stripped.start_with?(prefix) }
|
|
2909
2911
|
|
|
2910
|
-
without_inline_comment =
|
|
2912
|
+
without_inline_comment = comment_prefixes.reduce(stripped) do |content, prefix|
|
|
2913
|
+
content.sub(/ +#{Regexp.escape(prefix)}.*/, "")
|
|
2914
|
+
end
|
|
2911
2915
|
without_inline_comment.include?(DEPRECATED_RENDERER_CACHE_TASK)
|
|
2912
2916
|
end
|
|
2913
2917
|
end
|
|
2914
2918
|
|
|
2919
|
+
def renderer_cache_deploy_script_comment_prefixes(path)
|
|
2920
|
+
return ["#", "//"] if path == "Jenkinsfile"
|
|
2921
|
+
|
|
2922
|
+
["#"]
|
|
2923
|
+
end
|
|
2924
|
+
|
|
2915
2925
|
def deploy_script_path_references_deprecated_task?(path)
|
|
2916
2926
|
full_path = Rails.root.join(path)
|
|
2917
2927
|
|
|
@@ -2920,7 +2930,7 @@ module ReactOnRails
|
|
|
2920
2930
|
# deploy scripts and CI manifests should be tiny.
|
|
2921
2931
|
return false if full_path.size > RENDERER_CACHE_DEPLOY_SCRIPT_MAX_BYTES
|
|
2922
2932
|
|
|
2923
|
-
deploy_script_references_deprecated_task?(full_path)
|
|
2933
|
+
deploy_script_references_deprecated_task?(full_path, path)
|
|
2924
2934
|
rescue StandardError => e
|
|
2925
2935
|
checker.add_warning(
|
|
2926
2936
|
"⚠️ Could not scan #{path} for deprecated renderer-cache task references: #{e.message}"
|
|
@@ -170,7 +170,8 @@ module ReactOnRails
|
|
|
170
170
|
return if env_config_path.to_s.empty?
|
|
171
171
|
|
|
172
172
|
Rails.logger&.warn(
|
|
173
|
-
"[React on Rails] SHAKAPACKER_CONFIG is set to '#{env_config_path}'
|
|
173
|
+
"[React on Rails] SHAKAPACKER_CONFIG is set to '#{env_config_path}' " \
|
|
174
|
+
"(resolved to '#{shakapacker_config_path}') but the file " \
|
|
174
175
|
"does not exist. Bundler detection treated Shakapacker as not configured for this app; " \
|
|
175
176
|
"fix the path or unset the variable if this is unintended."
|
|
176
177
|
)
|
|
@@ -710,12 +710,46 @@ module ReactOnRails
|
|
|
710
710
|
raise ReactOnRails::PrerenderError.new(
|
|
711
711
|
component_name: react_component_name,
|
|
712
712
|
props: sanitized_props_string(props),
|
|
713
|
-
err:
|
|
713
|
+
err: rendering_error_from_result(json_result),
|
|
714
714
|
js_code: js_code,
|
|
715
715
|
console_messages: json_result["consoleReplayScript"]
|
|
716
716
|
)
|
|
717
717
|
end
|
|
718
718
|
|
|
719
|
+
def rendering_error_from_result(json_result)
|
|
720
|
+
rendering_error = json_result["renderingError"]
|
|
721
|
+
return unless rendering_error.is_a?(Hash)
|
|
722
|
+
|
|
723
|
+
stack = rendering_error["stack"]
|
|
724
|
+
# Mirror the JS `buildRSCStreamDiagnosticError` fallback: a renderingError that carries only
|
|
725
|
+
# a stack (no message) should still surface a diagnostic rather than be silently dropped.
|
|
726
|
+
message = rendering_error["message"].presence ||
|
|
727
|
+
(stack.present? ? "RSC stream metadata reported a rendering error" : nil)
|
|
728
|
+
return if message.nil?
|
|
729
|
+
|
|
730
|
+
error = StandardError.new(message)
|
|
731
|
+
frames = normalize_js_stack_lines(stack)
|
|
732
|
+
error.set_backtrace(frames) unless frames.empty?
|
|
733
|
+
error
|
|
734
|
+
end
|
|
735
|
+
|
|
736
|
+
# V8 stack strings begin with a `"TypeError: ..."` header line — and chained-exception stacks
|
|
737
|
+
# add further non-frame headers mid-stack (e.g. `Caused by: ...`) — none of which match Ruby's
|
|
738
|
+
# `file:line:in 'method'` backtrace format. Keep only the `at <frame>` lines so backtrace
|
|
739
|
+
# cleaners and APM tools that ship the array raw don't choke on unparseable strings.
|
|
740
|
+
# Frames are also `.strip`-ed to drop V8's leading indentation, which Ruby backtraces never carry.
|
|
741
|
+
#
|
|
742
|
+
# When the stack has no `at <frame>` lines (e.g. a header-only or non-V8 string) this returns
|
|
743
|
+
# `[]`, leaving the backtrace nil rather than seeding it with unparseable header lines.
|
|
744
|
+
def normalize_js_stack_lines(stack)
|
|
745
|
+
# Only V8 string stacks are parseable here. A non-String stack (e.g. an array of frames
|
|
746
|
+
# from a non-V8 serializer) would otherwise be `to_s`-ed into Ruby array syntax and yield
|
|
747
|
+
# no usable frames — guard explicitly so the no-backtrace path is intentional, not garbage.
|
|
748
|
+
return [] unless stack.is_a?(String)
|
|
749
|
+
|
|
750
|
+
stack.lines.map { |line| line.chomp.strip }.select { |line| line.start_with?("at ") }
|
|
751
|
+
end
|
|
752
|
+
|
|
719
753
|
def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
|
|
720
754
|
chunk_json_result["hasErrors"] &&
|
|
721
755
|
(if chunk_json_result["isShellReady"]
|
|
@@ -61,12 +61,7 @@ module ReactOnRails
|
|
|
61
61
|
|
|
62
62
|
MSG
|
|
63
63
|
|
|
64
|
-
backtrace =
|
|
65
|
-
err.backtrace.join("\n")
|
|
66
|
-
else
|
|
67
|
-
"#{Rails.backtrace_cleaner.clean(err.backtrace).join("\n")}\n" +
|
|
68
|
-
Rainbow("💡 Tip: Set FULL_TEXT_ERRORS=true to see the full backtrace").yellow
|
|
69
|
-
end
|
|
64
|
+
backtrace = formatted_backtrace(err)
|
|
70
65
|
else
|
|
71
66
|
backtrace = nil
|
|
72
67
|
end
|
|
@@ -95,6 +90,19 @@ module ReactOnRails
|
|
|
95
90
|
# rubocop:enable Metrics/AbcSize
|
|
96
91
|
end
|
|
97
92
|
|
|
93
|
+
# Formats `err.backtrace` for display, or returns nil when there are no frames.
|
|
94
|
+
def formatted_backtrace(err)
|
|
95
|
+
error_backtrace = err.backtrace
|
|
96
|
+
# JS-originated errors (e.g. an RSC renderingError carrying a message but no parseable
|
|
97
|
+
# stack) have no Ruby backtrace. Skip it rather than crashing on `nil.join` /
|
|
98
|
+
# `Rails.backtrace_cleaner.clean(nil)`.
|
|
99
|
+
return nil if error_backtrace.nil? || error_backtrace.empty?
|
|
100
|
+
return error_backtrace.join("\n") if Utils.full_text_errors_enabled?
|
|
101
|
+
|
|
102
|
+
"#{Rails.backtrace_cleaner.clean(error_backtrace).join("\n")}\n" +
|
|
103
|
+
Rainbow("💡 Tip: Set FULL_TEXT_ERRORS=true to see the full backtrace").yellow
|
|
104
|
+
end
|
|
105
|
+
|
|
98
106
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
99
107
|
def build_troubleshooting_suggestions(component_name, err, console_messages)
|
|
100
108
|
suggestions = []
|
|
@@ -13,7 +13,7 @@ module ReactOnRails
|
|
|
13
13
|
exit!(1)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
puts "\nBuilding
|
|
16
|
+
puts "\nBuilding assets..."
|
|
17
17
|
|
|
18
18
|
cmd = ReactOnRails::Utils.prepend_cd_node_modules_directory(
|
|
19
19
|
ReactOnRails.configuration.build_test_command
|
|
@@ -21,7 +21,7 @@ module ReactOnRails
|
|
|
21
21
|
|
|
22
22
|
ReactOnRails::Utils.invoke_and_exit_if_failed(cmd, compilation_failed_message)
|
|
23
23
|
|
|
24
|
-
puts "Completed building
|
|
24
|
+
puts "Completed building assets."
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
private
|
|
@@ -49,14 +49,14 @@ module ReactOnRails
|
|
|
49
49
|
|
|
50
50
|
def compilation_failed_message
|
|
51
51
|
<<~MSG
|
|
52
|
-
React on Rails: Error building
|
|
52
|
+
React on Rails: Error building test assets!
|
|
53
53
|
|
|
54
54
|
The build_test_command failed. This means test assets could not be compiled.
|
|
55
55
|
|
|
56
56
|
Quick alternatives to get unblocked:
|
|
57
57
|
• Run 'bin/dev static' in another terminal (assets auto-reuse for tests)
|
|
58
58
|
• Run 'bin/dev test-watch' to keep test assets fresh in the background
|
|
59
|
-
• Run '
|
|
59
|
+
• Run '#{ReactOnRails.configuration.build_test_command}' manually to compile once
|
|
60
60
|
|
|
61
61
|
For full details: #{TESTING_DOCS_URL}
|
|
62
62
|
Run 'bin/dev --help' for development server modes.
|
data/rakelib/lint.rake
CHANGED
|
@@ -37,7 +37,7 @@ namespace :lint do # rubocop:disable Metrics/BlockLength
|
|
|
37
37
|
private
|
|
38
38
|
|
|
39
39
|
def stylelint_command
|
|
40
|
-
"pnpm run
|
|
40
|
+
"pnpm run lint:scss"
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def stylelint_fix_command
|
data/rakelib/run_rspec.rake
CHANGED
|
@@ -69,8 +69,6 @@ namespace :run_rspec do
|
|
|
69
69
|
env_vars_array = []
|
|
70
70
|
env_vars_array << rbs_runtime_env_vars unless rbs_runtime_env_vars.empty?
|
|
71
71
|
env_vars_array << "DISABLE_TURBOLINKS=TRUE"
|
|
72
|
-
# This task intentionally runs with a different Gemfile view where the turbolinks gem is excluded.
|
|
73
|
-
env_vars_array << "BUNDLE_FROZEN=false"
|
|
74
72
|
env_vars = env_vars_array.join(" ")
|
|
75
73
|
run_tests_in(spec_dummy_dir,
|
|
76
74
|
env_vars: env_vars,
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: react_on_rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 17.0.0.rc.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin Gordon
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: addressable
|
|
@@ -192,7 +192,7 @@ files:
|
|
|
192
192
|
- lib/generators/react_on_rails/templates/dev_tests/spec/system/hello_server_spec.rb
|
|
193
193
|
- lib/generators/react_on_rails/templates/dev_tests/spec/system/hello_world_spec.rb
|
|
194
194
|
- lib/generators/react_on_rails/templates/pro/base/config/initializers/react_on_rails_pro.rb.tt
|
|
195
|
-
- lib/generators/react_on_rails/templates/pro/base/renderer/node-renderer.js
|
|
195
|
+
- lib/generators/react_on_rails/templates/pro/base/renderer/node-renderer.js.tt
|
|
196
196
|
- lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.js
|
|
197
197
|
- lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.ts
|
|
198
198
|
- lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx
|