Skip to content

Commit be4807a

Browse files
justin808claude
andcommitted
Fix component registration with defer default and add CI failure re-runner
This commit fixes component registration issues and adds tooling to efficiently replicate CI failures locally. ## Key Changes ### 1. Fix default loading strategy for older Shakapacker - Change default from :sync to :defer for Shakapacker < 8.2.0 - Ensures generated component packs register before main bundle executes - Prevents "Could not find component registered with name X" errors **Files:** - lib/react_on_rails/configuration.rb:184 - spec/dummy/spec/helpers/react_on_rails_helper_spec.rb:86 - spec/dummy/config/initializers/react_on_rails.rb:47 ### 2. New bin/ci-rerun-failures script Automatically detects failed CI jobs from GitHub and re-runs them locally: - Fetches CI status via gh CLI - Maps CI job names to local rake commands - Deduplicates and confirms before running - Eliminates wasted time running passing tests **Usage:** ```bash bin/ci-rerun-failures # Current PR bin/ci-rerun-failures 1964 # Specific PR ``` ### 3. Documentation improvements - Updated CLAUDE.md with ci-rerun-failures workflow - Added verified commands for replicating CI failures - Documented bin/ci-local for broader testing ### 4. Environment fixes - Updated Gemfile.lock: Capybara 3.39.2 → 3.40.0 (Rack 3 compatibility) - Added bin/* to .rubocop.yml exclusions (bash scripts) ## Why defer instead of sync? Scripts with no attributes execute immediately in document order, which can cause race conditions. The defer attribute ensures scripts wait until HTML parsing completes, then execute in document order - guaranteeing component registration happens before rendering. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 7572c93 commit be4807a

File tree

7 files changed

+272
-12
lines changed

7 files changed

+272
-12
lines changed

.rubocop.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ AllCops:
2525
- '**/node_modules/**/*'
2626
- '**/public/**/*'
2727
- '**/tmp/**/*'
28+
- 'bin/*' # Shell scripts
2829
- 'coverage/**/*'
2930
- 'gen-examples/examples/**/*'
3031
- 'node_modules/**/*'

CLAUDE.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,117 @@ Pre-commit hooks automatically run:
4747
- **⚠️ MANDATORY BEFORE GIT PUSH**: `bundle exec rubocop` and fix ALL violations + ensure trailing newlines
4848
- Never run `npm` commands, only equivalent Yarn Classic ones
4949

50+
### Replicating CI Failures Locally
51+
52+
**CRITICAL: NEVER wait for CI to verify fixes. Always replicate failures locally first.**
53+
54+
#### Re-run Failed CI Jobs (🔥 NEW - Most Efficient!)
55+
56+
```bash
57+
# Automatically detects and re-runs only the failed CI jobs
58+
bin/ci-rerun-failures
59+
60+
# Or for a specific PR number
61+
bin/ci-rerun-failures 1964
62+
```
63+
64+
This script:
65+
-**Fetches actual CI failures** from GitHub using `gh` CLI
66+
- 🎯 **Runs only what failed** - no wasted time on passing tests
67+
- 📋 **Shows you exactly what will run** before executing
68+
- 🚀 **Maps CI jobs to local commands** automatically
69+
70+
**Example output:**
71+
```
72+
Failed CI jobs:
73+
✗ dummy-app-integration-tests (3.4, 22, latest)
74+
✗ dummy-app-integration-tests (3.2, 20, minimum)
75+
76+
Will run the following commands:
77+
• dummy-app-integration-tests: bundle exec rake run_rspec:all_dummy
78+
```
79+
80+
#### Smart Test Detection
81+
82+
```bash
83+
# Runs tests based on your code changes
84+
bin/ci-local
85+
86+
# Run all CI checks
87+
bin/ci-local --all
88+
89+
# Fast mode - skips slow tests
90+
bin/ci-local --fast
91+
```
92+
93+
#### Manual Test Commands
94+
95+
When you need to run specific test suites manually:
96+
97+
```bash
98+
# All dummy app tests (matches CI most closely)
99+
bundle exec rake run_rspec:all_dummy
100+
```
101+
102+
This automatically builds TypeScript, generates packs, builds webpack, and runs tests.
103+
104+
#### Faster Iteration for Specific Tests
105+
106+
```bash
107+
# Run from project root
108+
rake node_package # 1. Build TypeScript and link via yalc
109+
110+
# Run from spec/dummy directory
111+
cd spec/dummy
112+
bin/shakapacker-precompile-hook # 2. Run precompile tasks (generate packs, ReScript, etc.)
113+
RAILS_ENV=test bin/shakapacker # 3. Build webpack
114+
DISABLE_TURBOLINKS=TRUE bundle exec rspec './spec/system/integration_spec.rb[1:1:1:1]'
115+
```
116+
117+
**Note**: Always run `bin/shakapacker-precompile-hook` before `bin/shakapacker` to ensure component packs are generated and any compilation tasks (ReScript, etc.) are completed. The hook handles all precompile steps that newer Shakapacker versions run automatically.
118+
119+
**Note**: System tests may fail locally with SSL/environment issues but pass in CI. When in doubt, rely on `rake run_rspec:all_dummy`.
120+
121+
#### Common Test Tasks
122+
123+
```bash
124+
# All dummy app tests (with and without turbolinks)
125+
bundle exec rake run_rspec:all_dummy
126+
127+
# Only tests without turbolinks (matches one CI job)
128+
bundle exec rake run_rspec:dummy_no_turbolinks
129+
130+
# Gem-only tests (fast, no webpack needed)
131+
bundle exec rake run_rspec:gem
132+
133+
# Everything except generated examples
134+
bundle exec rake all_but_examples
135+
```
136+
137+
#### Debugging Component Registration Issues
138+
139+
When you see "Could not find component registered with name X":
140+
141+
1. **Check generated packs exist**:
142+
```bash
143+
ls -la spec/dummy/client/app/packs/generated/
144+
ls -la spec/dummy/client/app/generated/
145+
```
146+
147+
2. **Verify webpack output**:
148+
```bash
149+
ls -la spec/dummy/public/webpack/test/js/
150+
```
151+
152+
3. **Check script loading in HTML**: Inspect the page source for `<script>` tags
153+
- Look for `defer` or `async` attributes
154+
- Verify generated component packs load before main bundle
155+
156+
4. **Test in browser console** (when app runs):
157+
```javascript
158+
ReactOnRails.registeredComponents() // See what's registered
159+
```
160+
50161
## Changelog
51162

52163
- **Update CHANGELOG.md for user-visible changes only** (features, bug fixes, breaking changes, deprecations, performance improvements)

bin/ci-rerun-failures

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#!/usr/bin/env bash
2+
# CI Failure Re-runner
3+
# Automatically detects failed CI jobs and re-runs them locally
4+
# Usage: bin/ci-rerun-failures [pr-number]
5+
6+
set -euo pipefail
7+
8+
# Colors for output
9+
RED='\033[0;31m'
10+
GREEN='\033[0;32m'
11+
YELLOW='\033[1;33m'
12+
BLUE='\033[0;34m'
13+
NC='\033[0m' # No Color
14+
15+
PR_NUMBER="${1:-}"
16+
17+
echo -e "${BLUE}=== CI Failure Re-runner ===${NC}"
18+
echo ""
19+
20+
# Fetch PR status using gh CLI
21+
if [ -z "$PR_NUMBER" ]; then
22+
echo "Fetching current PR status..."
23+
if ! gh pr view --json statusCheckRollup >/dev/null 2>&1; then
24+
echo -e "${RED}Error: Not on a PR branch or gh CLI not authenticated${NC}"
25+
echo "Usage: bin/ci-rerun-failures [pr-number]"
26+
exit 1
27+
fi
28+
STATUS_JSON=$(gh pr view --json statusCheckRollup,number)
29+
PR_NUMBER=$(echo "$STATUS_JSON" | jq -r '.number')
30+
else
31+
echo "Fetching PR #$PR_NUMBER status..."
32+
if ! gh pr view "$PR_NUMBER" --json statusCheckRollup >/dev/null 2>&1; then
33+
echo -e "${RED}Error: PR #$PR_NUMBER not found${NC}"
34+
exit 1
35+
fi
36+
STATUS_JSON=$(gh pr view "$PR_NUMBER" --json statusCheckRollup,number)
37+
fi
38+
39+
echo -e "${GREEN}Analyzing PR #$PR_NUMBER...${NC}"
40+
echo ""
41+
42+
# Parse failed checks
43+
FAILED_CHECKS=$(echo "$STATUS_JSON" | jq -r '.statusCheckRollup[] | select(.conclusion == "FAILURE") | .name')
44+
45+
if [ -z "$FAILED_CHECKS" ]; then
46+
echo -e "${GREEN}No failed checks found! All CI jobs passed.${NC}"
47+
exit 0
48+
fi
49+
50+
echo -e "${YELLOW}Failed CI jobs:${NC}"
51+
echo "$FAILED_CHECKS" | while read -r check; do
52+
echo -e "${RED}$check${NC}"
53+
done
54+
echo ""
55+
56+
# Map CI job names to local commands
57+
declare -A JOB_MAP
58+
JOB_MAP["lint-js-and-ruby"]="bundle exec rubocop && yarn run eslint --report-unused-disable-directives && yarn start format.listDifferent"
59+
JOB_MAP["rspec-package-tests"]="bundle exec rake run_rspec:gem"
60+
JOB_MAP["package-js-tests"]="yarn test"
61+
JOB_MAP["dummy-app-integration-tests (3.4, 22, latest)"]="bundle exec rake run_rspec:all_dummy"
62+
JOB_MAP["dummy-app-integration-tests (3.2, 20, minimum)"]="bundle exec rake run_rspec:all_dummy"
63+
JOB_MAP["examples"]="bundle exec rake run_rspec:shakapacker_examples"
64+
65+
# Track what we'll run (deduplicated)
66+
declare -A COMMANDS_TO_RUN
67+
68+
while IFS= read -r check; do
69+
for job_name in "${!JOB_MAP[@]}"; do
70+
if [[ "$check" == "$job_name"* ]]; then
71+
COMMANDS_TO_RUN["${JOB_MAP[$job_name]}"]="$job_name"
72+
break
73+
fi
74+
done
75+
done <<< "$FAILED_CHECKS"
76+
77+
if [ ${#COMMANDS_TO_RUN[@]} -eq 0 ]; then
78+
echo -e "${YELLOW}No local equivalents found for failed jobs.${NC}"
79+
echo "Failed jobs might be from Pro or other workflows."
80+
echo ""
81+
echo "You can still run common test suites:"
82+
echo " bundle exec rake run_rspec:all_dummy # Dummy app tests"
83+
echo " bundle exec rake run_rspec:gem # Gem tests"
84+
echo " yarn test # JS tests"
85+
exit 1
86+
fi
87+
88+
echo -e "${BLUE}Will run the following commands:${NC}"
89+
for cmd in "${!COMMANDS_TO_RUN[@]}"; do
90+
echo -e "${BLUE}${COMMANDS_TO_RUN[$cmd]}:${NC} $cmd"
91+
done
92+
echo ""
93+
94+
# Confirm before running
95+
read -p "Run these tests now? [Y/n] " -n 1 -r
96+
echo
97+
if [[ ! $REPLY =~ ^[Yy]$ ]] && [[ ! -z $REPLY ]]; then
98+
echo "Cancelled."
99+
exit 0
100+
fi
101+
102+
echo ""
103+
104+
# Ensure dependencies
105+
if [ ! -d "node_modules" ] || [ ! -d "vendor/bundle" ]; then
106+
echo -e "${YELLOW}Installing dependencies...${NC}"
107+
bundle install && yarn install
108+
echo ""
109+
fi
110+
111+
# Run commands
112+
FAILED_COMMANDS=()
113+
114+
for cmd in "${!COMMANDS_TO_RUN[@]}"; do
115+
job_name="${COMMANDS_TO_RUN[$cmd]}"
116+
echo -e "${BLUE}▶ Running: $job_name${NC}"
117+
echo -e "${BLUE}Command: $cmd${NC}"
118+
echo ""
119+
120+
if eval "$cmd"; then
121+
echo -e "${GREEN}$job_name passed${NC}"
122+
echo ""
123+
else
124+
echo -e "${RED}$job_name failed${NC}"
125+
echo ""
126+
FAILED_COMMANDS+=("$job_name")
127+
fi
128+
done
129+
130+
# Summary
131+
echo ""
132+
echo -e "${BLUE}=== Summary ===${NC}"
133+
134+
if [ ${#FAILED_COMMANDS[@]} -eq 0 ]; then
135+
echo -e "${GREEN}All local tests passed! ✓${NC}"
136+
echo ""
137+
echo "Push your changes and CI should pass."
138+
exit 0
139+
else
140+
echo -e "${RED}Some tests still failing:${NC}"
141+
for cmd in "${FAILED_COMMANDS[@]}"; do
142+
echo -e "${RED}$cmd${NC}"
143+
done
144+
echo ""
145+
echo -e "${YELLOW}Fix these failures before pushing.${NC}"
146+
exit 1
147+
fi

lib/react_on_rails/configuration.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,15 +172,16 @@ def validate_generated_component_packs_loading_strategy
172172

173173
msg = <<~MSG
174174
ReactOnRails: Your current version of shakapacker \
175-
does not support async script loading, which may cause performance issues. Please either:
176-
1. Use :sync or :defer loading strategy instead of :async
175+
does not support async script loading. Please either:
176+
1. Use :defer or :sync loading strategy instead of :async
177177
2. Upgrade to Shakapacker v8.2.0 or above to enable async script loading
178178
MSG
179179
if PackerUtils.supports_async_loading?
180180
self.generated_component_packs_loading_strategy ||= :async
181181
elsif generated_component_packs_loading_strategy.nil?
182-
Rails.logger.warn("**WARNING** #{msg}")
183-
self.generated_component_packs_loading_strategy = :sync
182+
# Use defer as the default for older Shakapacker versions to ensure
183+
# generated component packs load and register components before main bundle executes
184+
self.generated_component_packs_loading_strategy = :defer
184185
elsif generated_component_packs_loading_strategy == :async
185186
raise ReactOnRails::Error, "**ERROR** #{msg}"
186187
end

spec/dummy/Gemfile.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,11 @@ GEM
100100
msgpack (~> 1.2)
101101
builder (3.3.0)
102102
byebug (11.1.3)
103-
capybara (3.39.2)
103+
capybara (3.40.0)
104104
addressable
105105
matrix
106106
mini_mime (>= 0.1.3)
107-
nokogiri (~> 1.8)
107+
nokogiri (~> 1.11)
108108
rack (>= 1.6.0)
109109
rack-test (>= 0.6.3)
110110
regexp_parser (>= 1.5, < 3.0)
@@ -173,7 +173,7 @@ GEM
173173
net-pop
174174
net-smtp
175175
marcel (1.1.0)
176-
matrix (0.4.2)
176+
matrix (0.4.3)
177177
method_source (1.0.0)
178178
mini_mime (1.1.5)
179179
mini_portile2 (2.8.9)
@@ -223,7 +223,7 @@ GEM
223223
puma (6.4.0)
224224
nio4r (~> 2.0)
225225
racc (1.8.1)
226-
rack (3.2.1)
226+
rack (3.2.4)
227227
rack-proxy (0.7.7)
228228
rack
229229
rack-session (2.1.1)
@@ -272,7 +272,7 @@ GEM
272272
rdoc (6.14.2)
273273
erb
274274
psych (>= 4.0.0)
275-
regexp_parser (2.8.1)
275+
regexp_parser (2.11.3)
276276
reline (0.6.2)
277277
io-console (~> 0.5)
278278
rexml (3.2.6)

spec/dummy/config/initializers/react_on_rails.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@ def self.adjust_props_for_client_side_hydration(component_name, props)
4444
config.immediate_hydration = false
4545
# Don't explicitly set generated_component_packs_loading_strategy - let it default based on Shakapacker version
4646
# - Shakapacker >= 8.2.0: defaults to :async (optimal performance)
47-
# - Shakapacker < 8.2.0: defaults to :sync (compatibility)
47+
# - Shakapacker < 8.2.0: defaults to :defer (ensures proper component registration order)
4848
end

spec/dummy/spec/helpers/react_on_rails_helper_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ def self.pro_attribution_comment
8282
expect(helper).to have_received(:append_javascript_pack_tag).with("generated/component_name",
8383
{ defer: false, async: true })
8484
else
85-
# When async is not supported, defaults to :sync which means { defer: false }
86-
expect(helper).to have_received(:append_javascript_pack_tag).with("generated/component_name", { defer: false })
85+
# When async is not supported, defaults to :defer which means { defer: true }
86+
expect(helper).to have_received(:append_javascript_pack_tag).with("generated/component_name", { defer: true })
8787
end
8888
expect(helper).to have_received(:append_stylesheet_pack_tag).with("generated/component_name")
8989
end

0 commit comments

Comments
 (0)