Skip to content

Commit cbb7df2

Browse files
justin808claude
andcommitted
Fix Pro dummy app Playwright test timeouts by aligning with defer loading strategy
Apply the same defer loading strategy fix from commit d1a8a1a to the Pro dummy app to resolve race condition causing Playwright test timeouts. ## Problem Playwright E2E tests for streaming were timing out waiting for console message "ToggleContainer with title", indicating React components weren't hydrating. ## Root Cause The Pro dummy app was still using async: true for javascript_pack_tag while the open-source dummy app was updated to defer: true in commit d1a8a1a. This created a race condition where: - Generated component packs load asynchronously - Main client-bundle also loads asynchronously - If client-bundle executes before component registrations complete, React tries to hydrate unregistered components - ToggleContainer never hydrates, useEffect never runs, console.log never fires ## Solution 1. Changed javascript_pack_tag from async: true to defer: true in application.html.erb 2. Added precompile_hook to shakapacker.yml for pack generation 3. Added bin/shakapacker-precompile-hook script Using defer: true ensures script execution order - generated component packs load and register components before main bundle executes, preventing the race condition. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 8a91778 commit cbb7df2

File tree

3 files changed

+110
-5
lines changed

3 files changed

+110
-5
lines changed

react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,12 @@
2424
media: 'all',
2525
'data-turbo-track': 'reload') %>
2626

27-
<%# async: true is the recommended approach for Shakapacker >= 8.2.0 (currently using 9.3.0).
28-
It enables React 18's Selective Hydration and provides optimal Time to Interactive (TTI).
29-
Use immediate_hydration feature to control hydration timing for Selective/Immediate Hydration.
30-
See docs/building-features/streaming-server-rendering.md
27+
<%# Use defer: true to ensure proper script execution order.
28+
When using generated component packs (auto_load_bundle), defer ensures
29+
component registrations complete before React hydration begins.
3130
skip_js_packs param is used for testing purposes to simulate hydration failure %>
3231
<% unless params[:skip_js_packs] == 'true' %>
33-
<%= javascript_pack_tag('client-bundle', 'data-turbo-track': 'reload', async: true) %>
32+
<%= javascript_pack_tag('client-bundle', 'data-turbo-track': 'reload', defer: true) %>
3433
<% end %>
3534
<%= csrf_meta_tags %>
3635
</head>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
# Shakapacker precompile hook
5+
# This script runs before Shakapacker compilation in both development and production.
6+
# See: https://github.com/shakacode/shakapacker/blob/main/docs/precompile_hook.md
7+
8+
require "fileutils"
9+
10+
# Find Rails root by walking upward looking for config/environment.rb
11+
def find_rails_root
12+
dir = Dir.pwd
13+
loop do
14+
return dir if File.exist?(File.join(dir, "config", "environment.rb"))
15+
16+
parent = File.dirname(dir)
17+
return nil if parent == dir # Reached filesystem root
18+
19+
dir = parent
20+
end
21+
end
22+
23+
# Build ReScript if needed
24+
def build_rescript_if_needed
25+
# Check for both old (bsconfig.json) and new (rescript.json) config files
26+
return unless File.exist?("bsconfig.json") || File.exist?("rescript.json")
27+
28+
puts "🔧 Building ReScript..."
29+
30+
# Cross-platform package manager detection
31+
yarn_available = system("yarn", "--version", out: File::NULL, err: File::NULL)
32+
npm_available = system("npm", "--version", out: File::NULL, err: File::NULL)
33+
34+
success = if yarn_available
35+
system("yarn", "build:rescript")
36+
elsif npm_available
37+
system("npm", "run", "build:rescript")
38+
else
39+
warn "⚠️ Warning: Neither yarn nor npm found. Skipping ReScript build."
40+
return
41+
end
42+
43+
if success
44+
puts "✅ ReScript build completed successfully"
45+
else
46+
warn "❌ ReScript build failed"
47+
exit 1
48+
end
49+
end
50+
51+
# Generate React on Rails packs if needed
52+
# rubocop:disable Metrics/CyclomaticComplexity
53+
def generate_packs_if_needed
54+
# Find Rails root directory
55+
rails_root = find_rails_root
56+
return unless rails_root
57+
58+
# Check if React on Rails initializer exists
59+
initializer_path = File.join(rails_root, "config", "initializers", "react_on_rails.rb")
60+
return unless File.exist?(initializer_path)
61+
62+
# Check if auto-pack generation is configured (match actual config assignments, not comments)
63+
config_file = File.read(initializer_path)
64+
# Match uncommented configuration lines only (lines not starting with #)
65+
has_auto_load = config_file =~ /^\s*(?!#).*config\.auto_load_bundle\s*=/
66+
has_components_subdir = config_file =~ /^\s*(?!#).*config\.components_subdirectory\s*=/
67+
return unless has_auto_load || has_components_subdir
68+
69+
puts "📦 Generating React on Rails packs..."
70+
71+
# Cross-platform bundle availability check
72+
bundle_available = system("bundle", "--version", out: File::NULL, err: File::NULL)
73+
return unless bundle_available
74+
75+
# Check if rake task exists (use array form for security)
76+
task_list = IO.popen(["bundle", "exec", "rails", "-T"], err: [:child, :out], &:read)
77+
return unless task_list.include?("react_on_rails:generate_packs")
78+
79+
# Use array form for better cross-platform support
80+
success = system("bundle", "exec", "rails", "react_on_rails:generate_packs")
81+
82+
if success
83+
puts "✅ Pack generation completed successfully"
84+
else
85+
warn "❌ Pack generation failed"
86+
exit 1
87+
end
88+
end
89+
# rubocop:enable Metrics/CyclomaticComplexity
90+
91+
# Main execution
92+
begin
93+
build_rescript_if_needed
94+
generate_packs_if_needed
95+
96+
exit 0
97+
rescue StandardError => e
98+
warn "❌ Precompile hook failed: #{e.message}"
99+
warn e.backtrace.join("\n")
100+
exit 1
101+
end

react_on_rails_pro/spec/dummy/config/shakapacker.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ default: &default
1919
# Reload manifest.json on all requests so we reload latest compiled packs
2020
cache_manifest: false
2121

22+
# Hook to run before webpack compilation (e.g., for generating dynamic entry points)
23+
# SECURITY: Only reference trusted scripts within your project. Ensure the hook path
24+
# points to a file within the project root that you control.
25+
precompile_hook: 'bin/shakapacker-precompile-hook'
26+
2227
# Extract and emit a css file
2328
extract_css: true
2429

0 commit comments

Comments
 (0)