react_on_rails 16.6.0 → 16.7.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/.rubocop.yml +1 -0
- data/Gemfile.development_dependencies +2 -2
- data/Gemfile.lock +2 -14
- data/Rakefile +0 -6
- data/Steepfile +4 -0
- data/lib/generators/react_on_rails/base_generator.rb +4 -4
- data/lib/generators/react_on_rails/demo_page_config.rb +3 -3
- data/lib/generators/react_on_rails/dev_tests_generator.rb +1 -1
- data/lib/generators/react_on_rails/generator_helper.rb +6 -65
- data/lib/generators/react_on_rails/generator_messages/ci_section.rb +42 -0
- data/lib/generators/react_on_rails/generator_messages/package_manager_detection.rb +194 -0
- data/lib/generators/react_on_rails/generator_messages/shakapacker_status_section.rb +61 -0
- data/lib/generators/react_on_rails/generator_messages.rb +22 -79
- data/lib/generators/react_on_rails/install_generator.rb +243 -28
- data/lib/generators/react_on_rails/js_dependency_manager.rb +7 -4
- data/lib/generators/react_on_rails/pro/USAGE +1 -1
- data/lib/generators/react_on_rails/pro_generator.rb +206 -183
- data/lib/generators/react_on_rails/pro_setup.rb +102 -26
- data/lib/generators/react_on_rails/react_with_redux_generator.rb +3 -2
- data/lib/generators/react_on_rails/templates/base/base/.env.example +25 -0
- data/lib/generators/react_on_rails/templates/base/base/.github/workflows/ci.yml.tt +86 -0
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev +4 -3
- data/lib/generators/react_on_rails/templates/base/base/babel.config.js.tt +1 -1
- data/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler +2 -2
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/ServerClientOrBoth.js.tt +1 -1
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/clientWebpackConfig.js.tt +1 -1
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt +2 -2
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +1 -1
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/production.js.tt +1 -1
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +6 -5
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/test.js.tt +1 -1
- data/lib/generators/react_on_rails/templates/pro/base/config/initializers/react_on_rails_pro.rb.tt +1 -1
- data/lib/generators/react_on_rails/templates/pro/base/{client → renderer}/node-renderer.js +1 -0
- data/lib/react_on_rails/config_path_resolver.rb +101 -4
- data/lib/react_on_rails/configuration.rb +22 -0
- data/lib/react_on_rails/dev/file_manager.rb +135 -8
- data/lib/react_on_rails/dev/port_selector.rb +259 -7
- data/lib/react_on_rails/dev/process_manager.rb +29 -2
- data/lib/react_on_rails/dev/server_manager.rb +607 -39
- data/lib/react_on_rails/doctor.rb +513 -45
- data/lib/react_on_rails/helper.rb +3 -11
- data/lib/react_on_rails/js_code_builder.rb +66 -0
- data/lib/react_on_rails/length_prefixed_parser.rb +142 -0
- data/lib/react_on_rails/packs_generator.rb +65 -12
- data/lib/react_on_rails/pro_migration.rb +175 -0
- data/lib/react_on_rails/render_request.rb +74 -0
- data/lib/react_on_rails/rendering_strategy/exec_js_strategy.rb +29 -0
- data/lib/react_on_rails/rendering_strategy.rb +44 -0
- data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +33 -22
- data/lib/react_on_rails/system_checker.rb +44 -23
- data/lib/react_on_rails/utils.rb +5 -0
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails.rb +3 -0
- data/rakelib/run_rspec.rake +0 -5
- data/rakelib/shakapacker_examples.rake +66 -23
- data/react_on_rails.gemspec +18 -8
- data/sig/react_on_rails/js_code_builder.rbs +11 -0
- data/sig/react_on_rails/render_request.rbs +28 -0
- data/sig/react_on_rails/rendering_strategy/exec_js_strategy.rbs +11 -0
- data/sig/react_on_rails/rendering_strategy.rbs +7 -0
- data/sig/react_on_rails.rbs +6 -0
- metadata +31 -10
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "generator_messages"
|
|
4
|
+
require "react_on_rails/pro_migration"
|
|
4
5
|
|
|
5
6
|
module ReactOnRails
|
|
6
7
|
module Generators
|
|
@@ -33,7 +34,7 @@ module ReactOnRails
|
|
|
33
34
|
#
|
|
34
35
|
# Creates:
|
|
35
36
|
# - config/initializers/react_on_rails_pro.rb
|
|
36
|
-
# -
|
|
37
|
+
# - renderer/node-renderer.js
|
|
37
38
|
# - Procfile.dev entry for node-renderer
|
|
38
39
|
#
|
|
39
40
|
# @note NPM dependencies are handled separately by JsDependencyManager
|
|
@@ -43,8 +44,8 @@ module ReactOnRails
|
|
|
43
44
|
say set_color("=" * 80, :cyan)
|
|
44
45
|
|
|
45
46
|
create_pro_initializer
|
|
46
|
-
create_node_renderer
|
|
47
|
-
add_pro_to_procfile
|
|
47
|
+
legacy_renderer_detected = create_node_renderer
|
|
48
|
+
add_pro_to_procfile unless legacy_renderer_detected
|
|
48
49
|
update_webpack_config_for_pro
|
|
49
50
|
|
|
50
51
|
say set_color("=" * 80, :cyan)
|
|
@@ -52,22 +53,26 @@ module ReactOnRails
|
|
|
52
53
|
say set_color("=" * 80, :cyan)
|
|
53
54
|
end
|
|
54
55
|
|
|
55
|
-
# Check if Pro gem is missing.
|
|
56
|
+
# Check if the Pro gem is missing. When the base react_on_rails gem is in
|
|
57
|
+
# the Gemfile, installation is deferred to the later Gemfile swap (which
|
|
58
|
+
# preserves the user's version pin); otherwise auto-install via `bundle
|
|
59
|
+
# add` is attempted.
|
|
56
60
|
# @param force [Boolean] When true, always checks (default: only if use_pro?).
|
|
57
|
-
# @return [Boolean] true if Pro gem is missing and could not be
|
|
61
|
+
# @return [Boolean] true only if the Pro gem is missing and could not be
|
|
62
|
+
# installed; false if it is present, was auto-installed, or the install
|
|
63
|
+
# is deferred to the Gemfile swap.
|
|
58
64
|
def missing_pro_gem?(force: false)
|
|
59
65
|
return false unless force || use_pro?
|
|
60
66
|
return false if pro_gem_installed?
|
|
67
|
+
return false if defer_pro_gem_install_to_gemfile_swap
|
|
61
68
|
return false if attempt_pro_gem_auto_install
|
|
62
69
|
|
|
63
|
-
|
|
64
|
-
prerelease_note = rsc_pro_prerelease_note
|
|
70
|
+
optional_prerelease_line = prerelease_note.empty? ? "" : "\n#{prerelease_note}"
|
|
65
71
|
|
|
66
72
|
GeneratorMessages.add_error(<<~MSG.strip)
|
|
67
73
|
🚫 Failed to auto-install #{PRO_GEM_NAME} gem.
|
|
68
74
|
|
|
69
|
-
#{
|
|
70
|
-
#{prerelease_note}
|
|
75
|
+
#{pro_gem_requirement_context_line}#{optional_prerelease_line}
|
|
71
76
|
|
|
72
77
|
Please add manually to your Gemfile:
|
|
73
78
|
gem '#{PRO_GEM_NAME}', '#{pro_gem_version_requirement}'
|
|
@@ -94,20 +99,16 @@ module ReactOnRails
|
|
|
94
99
|
end
|
|
95
100
|
|
|
96
101
|
def pro_requirement_flag
|
|
97
|
-
return "--rsc-pro" if use_rsc_pro_mode?
|
|
98
102
|
return "--rsc" if options[:rsc]
|
|
99
103
|
|
|
100
104
|
"--pro"
|
|
101
105
|
end
|
|
102
106
|
|
|
103
|
-
def
|
|
104
|
-
return "" unless
|
|
105
|
-
return "" unless Gem::Version.new(ReactOnRails::VERSION).prerelease?
|
|
107
|
+
def prerelease_note
|
|
108
|
+
return "" unless prerelease_ror_version?
|
|
106
109
|
|
|
107
110
|
"Note: #{PRO_GEM_NAME} #{ReactOnRails::VERSION} may not be published yet. " \
|
|
108
111
|
"If you are testing from source, use a local Gemfile `path:` option."
|
|
109
|
-
rescue ArgumentError
|
|
110
|
-
""
|
|
111
112
|
end
|
|
112
113
|
|
|
113
114
|
# Attempt to auto-install the Pro gem via bundle add.
|
|
@@ -210,23 +211,65 @@ module ReactOnRails
|
|
|
210
211
|
say "✅ Created #{initializer_path}", :green
|
|
211
212
|
end
|
|
212
213
|
|
|
214
|
+
# Matches active (uncommented) Procfile.dev node-renderer lines, tolerating
|
|
215
|
+
# an optional `./` prefix that a user may have added by hand
|
|
216
|
+
# (e.g. `node ./renderer/node-renderer.js`).
|
|
217
|
+
NEW_RENDERER_COMMAND_REGEX = %r{^[ \t]*node-renderer:[^\n]*\bnode\s+\.?/?renderer/node-renderer\.js\b}
|
|
218
|
+
LEGACY_RENDERER_COMMAND_REGEX = %r{^[ \t]*node-renderer:[^\n]*\bnode\s+\.?/?client/node-renderer\.js\b}
|
|
219
|
+
|
|
220
|
+
# Creates renderer/node-renderer.js unless either the new path or the legacy
|
|
221
|
+
# client/node-renderer.js already exists.
|
|
222
|
+
#
|
|
223
|
+
# @return [Boolean] true when a legacy client/node-renderer.js was detected
|
|
224
|
+
# (caller should skip add_pro_to_procfile to avoid pointing Procfile.dev
|
|
225
|
+
# at a file that wasn't created); false otherwise.
|
|
213
226
|
def create_node_renderer
|
|
214
|
-
node_renderer_path = "
|
|
227
|
+
node_renderer_path = "renderer/node-renderer.js"
|
|
228
|
+
legacy_node_renderer_path = "client/node-renderer.js"
|
|
215
229
|
|
|
216
230
|
if File.exist?(File.join(destination_root, node_renderer_path))
|
|
217
231
|
say "ℹ️ #{node_renderer_path} already exists, skipping", :yellow
|
|
218
|
-
return
|
|
232
|
+
return false
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
if File.exist?(File.join(destination_root, legacy_node_renderer_path))
|
|
236
|
+
say "ℹ️ #{legacy_node_renderer_path} detected, keeping existing renderer; " \
|
|
237
|
+
"to migrate, move it to #{node_renderer_path} and update any references " \
|
|
238
|
+
"(e.g. Procfile.dev, Procfile.prod, Docker CMD / command):", :yellow
|
|
239
|
+
say " node-renderer: RENDERER_LOG_LEVEL=debug RENDERER_PORT=${RENDERER_PORT:-3800} " \
|
|
240
|
+
"node #{node_renderer_path}", :yellow
|
|
241
|
+
warn_on_stale_legacy_procfile_entry
|
|
242
|
+
return true
|
|
219
243
|
end
|
|
220
244
|
|
|
221
245
|
say "📝 Creating Node Renderer bootstrap...", :yellow
|
|
222
246
|
|
|
223
|
-
|
|
224
|
-
FileUtils.mkdir_p(File.join(destination_root, "client"))
|
|
247
|
+
empty_directory("renderer")
|
|
225
248
|
|
|
226
|
-
template_path = "templates/pro/base/
|
|
249
|
+
template_path = "templates/pro/base/renderer/node-renderer.js"
|
|
227
250
|
copy_file(template_path, node_renderer_path)
|
|
228
251
|
|
|
229
252
|
say "✅ Created #{node_renderer_path}", :green
|
|
253
|
+
false
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# When a legacy client/node-renderer.js is detected, add_pro_to_procfile is
|
|
257
|
+
# skipped, so surface a pointed warning if Procfile.dev still launches the
|
|
258
|
+
# legacy entry. This nudges the user to update the exact line they need to
|
|
259
|
+
# touch rather than leaving them to diff the generic migration hint against
|
|
260
|
+
# their Procfile themselves.
|
|
261
|
+
def warn_on_stale_legacy_procfile_entry
|
|
262
|
+
procfile_path = File.join(destination_root, "Procfile.dev")
|
|
263
|
+
return unless File.exist?(procfile_path)
|
|
264
|
+
|
|
265
|
+
procfile_content = File.read(procfile_path)
|
|
266
|
+
return unless procfile_content.match?(LEGACY_RENDERER_COMMAND_REGEX)
|
|
267
|
+
|
|
268
|
+
GeneratorMessages.add_warning(<<~MSG.strip)
|
|
269
|
+
⚠️ Procfile.dev still launches the legacy client/node-renderer.js.
|
|
270
|
+
After migrating the renderer file, update that line to:
|
|
271
|
+
node-renderer: RENDERER_LOG_LEVEL=debug RENDERER_PORT=${RENDERER_PORT:-3800} node renderer/node-renderer.js
|
|
272
|
+
MSG
|
|
230
273
|
end
|
|
231
274
|
|
|
232
275
|
def add_pro_to_procfile
|
|
@@ -237,22 +280,32 @@ module ReactOnRails
|
|
|
237
280
|
⚠️ Procfile.dev not found. Skipping Node Renderer process addition.
|
|
238
281
|
|
|
239
282
|
You'll need to add the Node Renderer to your process manager manually:
|
|
240
|
-
node-renderer: RENDERER_LOG_LEVEL=debug RENDERER_PORT
|
|
283
|
+
node-renderer: RENDERER_LOG_LEVEL=debug RENDERER_PORT=${RENDERER_PORT:-3800} node renderer/node-renderer.js
|
|
241
284
|
MSG
|
|
242
285
|
return
|
|
243
286
|
end
|
|
244
287
|
|
|
245
|
-
|
|
288
|
+
procfile_content = File.read(procfile_path)
|
|
289
|
+
|
|
290
|
+
if procfile_content.match?(NEW_RENDERER_COMMAND_REGEX)
|
|
246
291
|
say "ℹ️ Node Renderer already in Procfile.dev, skipping", :yellow
|
|
247
292
|
return
|
|
248
293
|
end
|
|
249
294
|
|
|
295
|
+
if procfile_content.match?(/^[ \t]*node-renderer:/)
|
|
296
|
+
say "⚠️ Procfile.dev has a node-renderer: entry that doesn't reference " \
|
|
297
|
+
"renderer/node-renderer.js. Update it manually to:", :yellow
|
|
298
|
+
say " node-renderer: RENDERER_LOG_LEVEL=debug RENDERER_PORT=${RENDERER_PORT:-3800} " \
|
|
299
|
+
"node renderer/node-renderer.js", :yellow
|
|
300
|
+
return
|
|
301
|
+
end
|
|
302
|
+
|
|
250
303
|
say "📝 Adding Node Renderer to Procfile.dev...", :yellow
|
|
251
304
|
|
|
252
305
|
node_renderer_line = <<~PROCFILE
|
|
253
306
|
|
|
254
307
|
# React on Rails Pro - Node Renderer for SSR
|
|
255
|
-
node-renderer: RENDERER_LOG_LEVEL=debug RENDERER_PORT
|
|
308
|
+
node-renderer: RENDERER_LOG_LEVEL=debug RENDERER_PORT=${RENDERER_PORT:-3800} node renderer/node-renderer.js
|
|
256
309
|
PROCFILE
|
|
257
310
|
|
|
258
311
|
append_to_file("Procfile.dev", node_renderer_line)
|
|
@@ -486,14 +539,37 @@ module ReactOnRails
|
|
|
486
539
|
"bundle add #{PRO_GEM_NAME} --version='#{pro_gem_version_requirement}' --strict"
|
|
487
540
|
end
|
|
488
541
|
|
|
542
|
+
def defer_pro_gem_install_to_gemfile_swap
|
|
543
|
+
return false unless base_react_on_rails_gem_in_gemfile?
|
|
544
|
+
|
|
545
|
+
mark_pro_gem_installed!
|
|
546
|
+
true
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def base_react_on_rails_gem_in_gemfile?
|
|
550
|
+
gemfile_path = File.join(destination_root, "Gemfile")
|
|
551
|
+
return false unless File.exist?(gemfile_path)
|
|
552
|
+
|
|
553
|
+
ReactOnRails::ProMigration.base_gem_entry?(File.read(gemfile_path))
|
|
554
|
+
rescue SystemCallError, IOError
|
|
555
|
+
false
|
|
556
|
+
end
|
|
557
|
+
|
|
489
558
|
def pro_gem_version_requirement
|
|
490
|
-
#
|
|
491
|
-
#
|
|
492
|
-
|
|
559
|
+
# Prerelease gem versions need an exact pin: Bundler's pessimistic operator
|
|
560
|
+
# (~>) does not match prerelease versions, so a stable range would fail to
|
|
561
|
+
# install during prerelease cycles.
|
|
562
|
+
return ReactOnRails::VERSION if prerelease_ror_version?
|
|
493
563
|
|
|
494
564
|
"~> #{recommended_pro_gem_version}"
|
|
495
565
|
end
|
|
496
566
|
|
|
567
|
+
def prerelease_ror_version?
|
|
568
|
+
Gem::Version.new(ReactOnRails::VERSION).prerelease?
|
|
569
|
+
rescue ArgumentError
|
|
570
|
+
false
|
|
571
|
+
end
|
|
572
|
+
|
|
497
573
|
# Keep manual fallback pinned to the latest stable release (drop pre-release suffixes like .rc.N).
|
|
498
574
|
# react_on_rails_pro follows the same version number as react_on_rails by policy.
|
|
499
575
|
# Both gems are released in lockstep; if this ever changes, replace with a dedicated constant.
|
|
@@ -104,7 +104,7 @@ module ReactOnRails
|
|
|
104
104
|
# Fallback to package manager detection if GeneratorHelper fails
|
|
105
105
|
return if success
|
|
106
106
|
|
|
107
|
-
package_manager = GeneratorMessages.detect_package_manager
|
|
107
|
+
package_manager = GeneratorMessages.detect_package_manager(app_root: destination_root)
|
|
108
108
|
return unless package_manager
|
|
109
109
|
|
|
110
110
|
install_packages_with_fallback(regular_packages, dev: false, package_manager: package_manager)
|
|
@@ -116,7 +116,8 @@ module ReactOnRails
|
|
|
116
116
|
# Append Redux-specific post-install instructions
|
|
117
117
|
GeneratorMessages.add_info(
|
|
118
118
|
GeneratorMessages.helpful_message_after_installation(component_name: "HelloWorldApp", route: "hello_world",
|
|
119
|
-
pro: Gem.loaded_specs.key?("react_on_rails_pro")
|
|
119
|
+
pro: Gem.loaded_specs.key?("react_on_rails_pro"),
|
|
120
|
+
app_root: destination_root)
|
|
120
121
|
)
|
|
121
122
|
end
|
|
122
123
|
|
|
@@ -7,12 +7,37 @@
|
|
|
7
7
|
# Shakapacker reads SHAKAPACKER_DEV_SERVER_PORT on both the Ruby (proxy) and
|
|
8
8
|
# JS (webpack-dev-server) sides, so no shakapacker.yml changes are needed.
|
|
9
9
|
#
|
|
10
|
+
# === Coding Agent / CI Integration ===
|
|
11
|
+
# Set REACT_ON_RAILS_BASE_PORT to derive all ports from a single value.
|
|
12
|
+
# This works with any tool (Conductor.build, Codex, Quad Code, etc.).
|
|
13
|
+
# Ports are assigned as: Rails = base+0, webpack = base+1, renderer = base+2.
|
|
14
|
+
# When set, no manual port configuration is needed.
|
|
15
|
+
#
|
|
16
|
+
# Conductor.build sets CONDUCTOR_PORT automatically, which is recognized
|
|
17
|
+
# as a fallback when REACT_ON_RAILS_BASE_PORT is not set.
|
|
18
|
+
#
|
|
19
|
+
# === Manual Worktree Setup ===
|
|
10
20
|
# Example for a second worktree:
|
|
11
21
|
# PORT=3001
|
|
12
22
|
# SHAKAPACKER_DEV_SERVER_PORT=3036
|
|
23
|
+
# RENDERER_PORT=3801 # React on Rails Pro only
|
|
24
|
+
# REACT_RENDERER_URL=http://localhost:3801 # Must match RENDERER_PORT
|
|
25
|
+
|
|
26
|
+
# Base port for coding agent tools (derives all other ports automatically)
|
|
27
|
+
# REACT_ON_RAILS_BASE_PORT=
|
|
13
28
|
|
|
14
29
|
# Rails server port (default: 3000 for Procfile.dev / 3001 for Procfile.dev-prod-assets)
|
|
15
30
|
# PORT=3000
|
|
16
31
|
|
|
17
32
|
# Webpack dev server port (default: 3035, used by shakapacker)
|
|
18
33
|
# SHAKAPACKER_DEV_SERVER_PORT=3035
|
|
34
|
+
|
|
35
|
+
# Node renderer port (React on Rails Pro only, default: 3800)
|
|
36
|
+
# RENDERER_PORT=3800
|
|
37
|
+
|
|
38
|
+
# Node renderer URL (React on Rails Pro only, must match RENDERER_PORT).
|
|
39
|
+
# If you set only RENDERER_PORT, bin/dev auto-derives
|
|
40
|
+
# REACT_RENDERER_URL=http://localhost:RENDERER_PORT. For a remote or
|
|
41
|
+
# non-localhost renderer (Docker service name, remote host), set
|
|
42
|
+
# REACT_RENDERER_URL explicitly so it is not replaced with the localhost default.
|
|
43
|
+
# REACT_RENDERER_URL=http://localhost:3800
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, master]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Set up Ruby
|
|
17
|
+
uses: ruby/setup-ruby@v1
|
|
18
|
+
with:
|
|
19
|
+
# ruby-version: "3.3" # Uncomment and set if you don't have a .ruby-version file
|
|
20
|
+
bundler-cache: true
|
|
21
|
+
|
|
22
|
+
<%- if config[:package_manager] == 'pnpm' -%>
|
|
23
|
+
- name: Set up pnpm
|
|
24
|
+
uses: pnpm/action-setup@v4
|
|
25
|
+
<%- unless config[:pnpm_version_declared] -%>
|
|
26
|
+
with:
|
|
27
|
+
# ⚠️ Verify this matches the pnpm major that wrote your pnpm-lock.yaml —
|
|
28
|
+
# major mismatches can silently change dependency resolution. To manage
|
|
29
|
+
# the version via Corepack instead, add `"packageManager": "pnpm@<exact-version>"`
|
|
30
|
+
# to package.json and remove this `with:` block. Range/tag specs (e.g. `pnpm@^10`,
|
|
31
|
+
# `pnpm@latest`) are non-reproducible and some Corepack versions reject them.
|
|
32
|
+
version: "<%= config[:pnpm_fallback_version] %>"
|
|
33
|
+
<%- end -%>
|
|
34
|
+
<%- end -%>
|
|
35
|
+
<%- if config[:package_manager] == 'bun' -%>
|
|
36
|
+
- name: Set up Bun
|
|
37
|
+
uses: oven-sh/setup-bun@v2
|
|
38
|
+
|
|
39
|
+
<%- end -%>
|
|
40
|
+
<%- if config[:package_manager] == 'yarn' -%>
|
|
41
|
+
- name: Enable Corepack
|
|
42
|
+
# Must run before actions/setup-node so its `cache: "yarn"` step
|
|
43
|
+
# can resolve Yarn Berry's cache directory correctly.
|
|
44
|
+
run: corepack enable
|
|
45
|
+
|
|
46
|
+
<%- end -%>
|
|
47
|
+
- name: Set up Node
|
|
48
|
+
uses: actions/setup-node@v4
|
|
49
|
+
with:
|
|
50
|
+
# "lts/*" tracks the current Node LTS; consider pinning (e.g. "22") for reproducible builds.
|
|
51
|
+
node-version: "lts/*"
|
|
52
|
+
<%- if config[:has_lockfile] && %w[yarn pnpm npm].include?(config[:package_manager]) -%>
|
|
53
|
+
cache: "<%= config[:package_manager] %>"
|
|
54
|
+
<%- end -%>
|
|
55
|
+
|
|
56
|
+
- name: Install JS dependencies
|
|
57
|
+
<%- if !config[:has_lockfile] && config[:package_manager] == 'yarn' -%>
|
|
58
|
+
# Yarn Berry defaults to immutable installs; allow lockfile creation on first CI run.
|
|
59
|
+
run: yarn install --no-immutable
|
|
60
|
+
<%- elsif !config[:has_lockfile] && config[:package_manager] == 'pnpm' -%>
|
|
61
|
+
# pnpm defaults to frozen-lockfile in CI; allow lockfile creation on first CI run.
|
|
62
|
+
run: pnpm install --no-frozen-lockfile
|
|
63
|
+
<%- else -%>
|
|
64
|
+
run: <%= config[:package_manager] %> install
|
|
65
|
+
<%- end -%>
|
|
66
|
+
|
|
67
|
+
<%- if config[:has_active_record] -%>
|
|
68
|
+
- name: Set up database
|
|
69
|
+
# If using PostgreSQL or MySQL, add a services block above.
|
|
70
|
+
# See https://docs.github.com/en/actions/using-containerized-services
|
|
71
|
+
run: bin/rails db:prepare
|
|
72
|
+
env:
|
|
73
|
+
RAILS_ENV: test
|
|
74
|
+
|
|
75
|
+
<%- end -%>
|
|
76
|
+
- name: Build JavaScript bundles
|
|
77
|
+
# Shakapacker default. ViteRuby users: replace with `bin/vite build --mode test`.
|
|
78
|
+
run: bin/shakapacker
|
|
79
|
+
env:
|
|
80
|
+
RAILS_ENV: test
|
|
81
|
+
NODE_ENV: test
|
|
82
|
+
|
|
83
|
+
- name: Run tests
|
|
84
|
+
run: <%= config[:has_rspec] ? "bundle exec rspec" : "bin/rails test" %>
|
|
85
|
+
env:
|
|
86
|
+
RAILS_ENV: test
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Procfile for development
|
|
2
2
|
# You can run these commands in separate shells
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# PORT, SHAKAPACKER_DEV_SERVER_PORT, and renderer ports are set automatically by
|
|
5
|
+
# bin/dev when REACT_ON_RAILS_BASE_PORT (or CONDUCTOR_PORT) is present, so
|
|
6
|
+
# concurrent worktrees get distinct ports without manual .env edits.
|
|
7
|
+
# For manual worktree setup (no base-port tool), see .env.example.
|
|
7
8
|
rails: bundle exec rails s -p ${PORT:-3000}
|
|
8
9
|
dev-server: bin/shakapacker-dev-server
|
|
9
10
|
server-bundle: SERVER_BUNDLE_ONLY=true bin/shakapacker-watch --watch
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= add_documentation_reference(config[:message], "// https://github.com/shakacode/
|
|
1
|
+
<%= add_documentation_reference(config[:message], "// https://github.com/shakacode/react-on-rails-demo-ssr-hmr/blob/master/babel.config.js") %>
|
|
2
2
|
|
|
3
3
|
module.exports = function (api) {
|
|
4
4
|
const defaultConfigFunc = require('shakapacker/package/babel/preset.js')
|
|
@@ -15,8 +15,8 @@ class BundlerSwitcher
|
|
|
15
15
|
}.freeze
|
|
16
16
|
|
|
17
17
|
RSPACK_DEPS = {
|
|
18
|
-
dependencies: %w[@rspack/core@^
|
|
19
|
-
dev_dependencies: %w[@rspack/cli@^
|
|
18
|
+
dependencies: %w[@rspack/core@^2.0.0-0 rspack-manifest-plugin@^5.0.0],
|
|
19
|
+
dev_dependencies: %w[@rspack/cli@^2.0.0-0 @rspack/plugin-react-refresh@^2.0.0]
|
|
20
20
|
}.freeze
|
|
21
21
|
|
|
22
22
|
def initialize(target_bundler)
|
data/lib/generators/react_on_rails/templates/base/base/config/webpack/ServerClientOrBoth.js.tt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= add_documentation_reference(config[:message], "// https://github.com/shakacode/
|
|
1
|
+
<%= add_documentation_reference(config[:message], "// https://github.com/shakacode/react-on-rails-demo-ssr-hmr/blob/master/config/webpack/ServerClientOrBoth.js") %>
|
|
2
2
|
|
|
3
3
|
const clientWebpackConfig = require('./clientWebpackConfig');
|
|
4
4
|
<% if use_pro? -%>
|
data/lib/generators/react_on_rails/templates/base/base/config/webpack/clientWebpackConfig.js.tt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= add_documentation_reference(config[:message], "// https://github.com/shakacode/
|
|
1
|
+
<%= add_documentation_reference(config[:message], "// https://github.com/shakacode/react-on-rails-demo-ssr-hmr/blob/master/config/webpack/clientWebpackConfig.js") %>
|
|
2
2
|
|
|
3
3
|
const commonWebpackConfig = require('./commonWebpackConfig');
|
|
4
4
|
<% if use_rsc? -%>
|
data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= add_documentation_reference(config[:message], "// https://github.com/shakacode/
|
|
1
|
+
<%= add_documentation_reference(config[:message], "// https://github.com/shakacode/react-on-rails-demo-ssr-hmr/blob/master/config/webpack/commonWebpackConfig.js") %>
|
|
2
2
|
|
|
3
3
|
// Common configuration applying to client and server configuration
|
|
4
4
|
const { generateWebpackConfig, merge } = require('shakapacker');
|
|
@@ -14,4 +14,4 @@ const commonOptions = {
|
|
|
14
14
|
// Copy the object using merge b/c the baseClientWebpackConfig and commonOptions are mutable globals
|
|
15
15
|
const commonWebpackConfig = () => merge({}, baseClientWebpackConfig, commonOptions);
|
|
16
16
|
|
|
17
|
-
module.exports = commonWebpackConfig;
|
|
17
|
+
module.exports = commonWebpackConfig;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= add_documentation_reference(config[:message], "// https://github.com/shakacode/
|
|
1
|
+
<%= add_documentation_reference(config[:message], "// https://github.com/shakacode/react-on-rails-demo-ssr-hmr/blob/master/config/webpack/development.js") %>
|
|
2
2
|
|
|
3
3
|
const { devServer, inliningCss, config } = require('shakapacker');
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= add_documentation_reference(config[:message], "// https://github.com/shakacode/
|
|
1
|
+
<%= add_documentation_reference(config[:message], "// https://github.com/shakacode/react-on-rails-demo-ssr-hmr/blob/master/config/webpack/production.js") %>
|
|
2
2
|
|
|
3
3
|
const serverClientOrBoth = require('./ServerClientOrBoth');
|
|
4
4
|
|
data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= add_documentation_reference(config[:message], "// https://github.com/shakacode/
|
|
1
|
+
<%= add_documentation_reference(config[:message], "// https://github.com/shakacode/react-on-rails-demo-ssr-hmr/blob/master/config/webpack/serverWebpackConfig.js") %>
|
|
2
2
|
|
|
3
3
|
const { merge, config } = require('shakapacker');
|
|
4
4
|
const commonWebpackConfig = require('./commonWebpackConfig');
|
|
@@ -197,10 +197,11 @@ const configureServer = () => {
|
|
|
197
197
|
}
|
|
198
198
|
});
|
|
199
199
|
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
|
|
200
|
+
// Avoid the webpack eval devtool, which triggers a webpack 5.106+ regression
|
|
201
|
+
// with ESM default exports (ReferenceError: __WEBPACK_DEFAULT_EXPORT__ is not defined).
|
|
202
|
+
// In development, cheap-module-source-map provides original line numbers in SSR error traces.
|
|
203
|
+
// In production, devtool is disabled to avoid generating .map files.
|
|
204
|
+
serverWebpackConfig.devtool = process.env.NODE_ENV === 'production' ? false : 'cheap-module-source-map';
|
|
204
205
|
|
|
205
206
|
<% if use_pro? -%>
|
|
206
207
|
// React on Rails Pro uses Node renderer, so target must be 'node'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= add_documentation_reference(config[:message], "// https://github.com/shakacode/
|
|
1
|
+
<%= add_documentation_reference(config[:message], "// https://github.com/shakacode/react-on-rails-demo-ssr-hmr/blob/master/config/webpack/test.js") %>
|
|
2
2
|
|
|
3
3
|
const serverClientOrBoth = require('./ServerClientOrBoth')
|
|
4
4
|
|
data/lib/generators/react_on_rails/templates/pro/base/config/initializers/react_on_rails_pro.rb.tt
CHANGED
|
@@ -6,7 +6,7 @@ ReactOnRailsPro.configure do |config|
|
|
|
6
6
|
config.server_renderer = "NodeRenderer"
|
|
7
7
|
config.renderer_url = ENV.fetch("REACT_RENDERER_URL", "http://localhost:3800")
|
|
8
8
|
|
|
9
|
-
# See value in
|
|
9
|
+
# See value in renderer/node-renderer.js
|
|
10
10
|
config.renderer_password = ENV.fetch("RENDERER_PASSWORD", "devPassword")
|
|
11
11
|
|
|
12
12
|
config.ssr_timeout = 5
|
|
@@ -6,6 +6,7 @@ const configuredWorkersCount =
|
|
|
6
6
|
parseWorkersCount(env.RENDERER_WORKERS_COUNT) ?? parseWorkersCount(env.NODE_RENDERER_CONCURRENCY);
|
|
7
7
|
|
|
8
8
|
const config = {
|
|
9
|
+
// Resolves to <project-root>/.node-renderer-bundles (one level up from renderer/).
|
|
9
10
|
serverBundleCachePath: path.resolve(__dirname, '../.node-renderer-bundles'),
|
|
10
11
|
port: Number(env.RENDERER_PORT) || 3800,
|
|
11
12
|
logLevel: env.RENDERER_LOG_LEVEL || 'info',
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
3
5
|
module ReactOnRails
|
|
4
6
|
module ConfigPathResolver
|
|
5
7
|
# Keep JS before TS to match generator defaults and to prefer the
|
|
@@ -14,13 +16,108 @@ module ReactOnRails
|
|
|
14
16
|
].freeze
|
|
15
17
|
ALL_DEFAULT_CONFIG_CANDIDATES = (WEBPACK_DEFAULT_CONFIG_CANDIDATES + RSPACK_DEFAULT_CONFIG_CANDIDATES).freeze
|
|
16
18
|
|
|
19
|
+
protected
|
|
20
|
+
|
|
21
|
+
# Protected so including classes and overrides can share the same warning
|
|
22
|
+
# de-dupe registry across resolver callers without exposing it as API.
|
|
23
|
+
def config_path_warning_registry
|
|
24
|
+
@config_path_warning_registry ||= {
|
|
25
|
+
package_roots: Set.new,
|
|
26
|
+
package_json_paths: Set.new
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
17
30
|
private
|
|
18
31
|
|
|
19
|
-
def resolved_package_json_path
|
|
20
|
-
|
|
21
|
-
|
|
32
|
+
def resolved_package_json_path(package_root = resolved_package_root)
|
|
33
|
+
resolved_package_path("package.json", package_root)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Memoized per instance: configuration is set once at boot in production.
|
|
37
|
+
# Tests must build a fresh resolver after stubbing a different
|
|
38
|
+
# node_modules_location, since reconfiguration on a reused instance is
|
|
39
|
+
# ignored after the first call.
|
|
40
|
+
def resolved_package_root
|
|
41
|
+
@resolved_package_root ||= begin
|
|
42
|
+
node_modules_location = ReactOnRails.configuration.node_modules_location.to_s
|
|
43
|
+
|
|
44
|
+
resolved_location = Pathname.new(node_modules_location).cleanpath
|
|
45
|
+
# cleanpath normalizes redundant separators and ".." without resolving symlinks;
|
|
46
|
+
# realpath is intentionally skipped to avoid filesystem I/O on every call.
|
|
47
|
+
# Relative paths like "../client" remain valid diagnostics targets and are
|
|
48
|
+
# not constrained to stay within Rails.root.
|
|
49
|
+
if resolved_location == Pathname.new(".")
|
|
50
|
+
Rails.root.to_s
|
|
51
|
+
elsif resolved_location.absolute?
|
|
52
|
+
resolved_location.to_s
|
|
53
|
+
else
|
|
54
|
+
Rails.root.join(resolved_location).to_s
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def resolved_package_path(filename, package_root = resolved_package_root)
|
|
60
|
+
File.join(package_root, filename)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def package_root_missing?(package_root)
|
|
64
|
+
!Dir.exist?(package_root)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def package_json_path_for(detection_target, package_root = resolved_package_root)
|
|
68
|
+
package_json_path = resolved_package_json_path(package_root)
|
|
69
|
+
return package_json_path if File.exist?(package_json_path)
|
|
70
|
+
|
|
71
|
+
if package_root_missing?(package_root)
|
|
72
|
+
warn_missing_package_root(package_root)
|
|
73
|
+
else
|
|
74
|
+
warn_missing_package_json(package_json_path, detection_target)
|
|
75
|
+
end
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Including classes must provide #add_warning(message). Classes that route
|
|
80
|
+
# warnings into another object's message list can override
|
|
81
|
+
# #config_path_warning_registry to share de-dupe state with that sink.
|
|
82
|
+
# Overrides must return a Hash with :package_roots and :package_json_paths
|
|
83
|
+
# keys whose values respond to #add?, such as Set instances.
|
|
84
|
+
def warn_missing_package_root(package_root)
|
|
85
|
+
return unless warned_package_roots.add?(package_root)
|
|
86
|
+
|
|
87
|
+
add_config_path_warning(missing_package_root_warning(package_root))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def missing_package_root_warning(package_root)
|
|
91
|
+
"⚠️ node_modules_location points to #{package_root}, but that directory does not exist; " \
|
|
92
|
+
"all diagnostics that read from it are skipped. Check config/initializers/react_on_rails.rb."
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def warn_missing_package_json(package_json_path, detection_target)
|
|
96
|
+
return unless warned_package_json_paths.add?(package_json_path)
|
|
97
|
+
|
|
98
|
+
add_config_path_warning(missing_package_json_warning(package_json_path, detection_target))
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def missing_package_json_warning(package_json_path, detection_target)
|
|
102
|
+
"⚠️ #{package_json_path} not found; cannot detect #{detection_target}. " \
|
|
103
|
+
"Check config/initializers/react_on_rails.rb."
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def warned_package_roots
|
|
107
|
+
config_path_warning_registry[:package_roots]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def warned_package_json_paths
|
|
111
|
+
config_path_warning_registry[:package_json_paths]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def add_warning(_message)
|
|
115
|
+
raise NotImplementedError,
|
|
116
|
+
"#{self.class} must implement #add_warning(message) to include ReactOnRails::ConfigPathResolver"
|
|
117
|
+
end
|
|
22
118
|
|
|
23
|
-
|
|
119
|
+
def add_config_path_warning(message)
|
|
120
|
+
add_warning(message)
|
|
24
121
|
end
|
|
25
122
|
|
|
26
123
|
def resolved_webpack_config_path
|
|
@@ -26,6 +26,28 @@ module ReactOnRails
|
|
|
26
26
|
configuration.setup_config_values
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
# Rendering strategy configured at boot time by engine initializers.
|
|
30
|
+
# Replaces runtime react_on_rails_pro? checks (see issue #2905).
|
|
31
|
+
# Not yet wired into the main rendering path — currently additive only.
|
|
32
|
+
def self.rendering_strategy
|
|
33
|
+
@rendering_strategy ||= ReactOnRails::RenderingStrategy::ExecJsStrategy.new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.rendering_strategy=(strategy)
|
|
37
|
+
@rendering_strategy = strategy
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# JS code builder configured at boot time by engine initializers.
|
|
41
|
+
# Used by RenderRequest#to_js to generate SSR JavaScript code.
|
|
42
|
+
# Not yet wired into the main rendering path — currently additive only.
|
|
43
|
+
def self.js_code_builder
|
|
44
|
+
@js_code_builder ||= ReactOnRails::JsCodeBuilder.new
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.js_code_builder=(builder)
|
|
48
|
+
@js_code_builder = builder
|
|
49
|
+
end
|
|
50
|
+
|
|
29
51
|
DEFAULT_GENERATED_ASSETS_DIR = File.join(%w[public webpack], Rails.env).freeze
|
|
30
52
|
DEFAULT_COMPONENT_REGISTRY_TIMEOUT = 5000
|
|
31
53
|
DEFAULT_SERVER_BUNDLE_OUTPUT_PATH = "ssr-generated"
|