Skip to content

Commit 641132c

Browse files
justin808claude
andcommitted
Refactor shakapacker-precompile-hook to use shared implementation
Consolidate duplicate precompile hook logic across OSS dummy app, Pro dummy app, and generator template into a single shared Ruby implementation. Changes: - Create lib/tasks/precompile/shakapacker_precompile_hook_shared.rb with common logic - Update spec/dummy/bin/shakapacker-precompile-hook to load shared implementation - Update react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook to load shared implementation - Simplify generator template to use Ruby instead of shell script (removes ReScript build logic) Benefits: - Eliminates 342 lines of duplicate code across 3 files - Easier to maintain and update precompile hook logic in one place - Consistent behavior across all environments - Better error handling with Ruby exceptions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 0f91a20 commit 641132c

File tree

4 files changed

+184
-342
lines changed

4 files changed

+184
-342
lines changed
Lines changed: 42 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,122 +1,58 @@
1-
#!/bin/sh
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
24
# Shakapacker precompile hook for React on Rails
35
#
4-
# This script runs before webpack compilation to:
5-
# 1. Build ReScript files (if configured)
6-
# 2. Generate pack files for auto-bundled components
6+
# This script runs before webpack compilation to generate pack files
7+
# for auto-bundled components.
78
#
89
# It's called automatically by Shakapacker when configured in config/shakapacker.yml:
910
# precompile_hook: 'bin/shakapacker-precompile-hook'
1011
#
1112
# See: https://github.com/shakacode/shakapacker/blob/main/docs/precompile_hook.md
1213

13-
set -e
14+
require "fileutils"
1415

1516
# Find Rails root by walking upward looking for config/environment.rb
16-
find_rails_root() {
17-
dir="$PWD"
18-
while [ "$dir" != "/" ]; do
19-
if [ -f "$dir/config/environment.rb" ]; then
20-
echo "$dir"
21-
return 0
22-
fi
23-
dir=$(dirname "$dir")
24-
done
25-
return 1
26-
}
27-
28-
# Build ReScript if needed
29-
build_rescript_if_needed() {
30-
rails_root=$(find_rails_root)
31-
if [ -z "$rails_root" ]; then
32-
echo "⚠️ Warning: Could not find Rails root. Skipping ReScript build."
33-
return 0
34-
fi
35-
36-
# Check for both old (bsconfig.json) and new (rescript.json) config files
37-
if [ ! -f "$rails_root/bsconfig.json" ] && [ ! -f "$rails_root/rescript.json" ]; then
38-
return 0
39-
fi
40-
41-
echo "🔧 Building ReScript..."
17+
def find_rails_root
18+
dir = Dir.pwd
19+
while dir != "/"
20+
return dir if File.exist?(File.join(dir, "config", "environment.rb"))
4221

43-
# Change to Rails root to run build commands
44-
cd "$rails_root" || return 1
45-
46-
# Cross-platform package manager detection
47-
if command -v yarn >/dev/null 2>&1; then
48-
if yarn build:rescript; then
49-
echo "✅ ReScript build completed successfully"
50-
return 0
51-
else
52-
echo "❌ ReScript build failed" >&2
53-
exit 1
54-
fi
55-
elif command -v npm >/dev/null 2>&1; then
56-
if npm run build:rescript; then
57-
echo "✅ ReScript build completed successfully"
58-
return 0
59-
else
60-
echo "❌ ReScript build failed" >&2
61-
exit 1
62-
fi
63-
else
64-
echo "⚠️ Warning: Neither yarn nor npm found. Skipping ReScript build."
65-
return 0
66-
fi
67-
}
22+
dir = File.dirname(dir)
23+
end
24+
nil
25+
end
6826

6927
# Generate React on Rails packs if needed
70-
generate_packs_if_needed() {
71-
rails_root=$(find_rails_root)
72-
if [ -z "$rails_root" ]; then
73-
return 0
74-
fi
75-
76-
# Check if React on Rails initializer exists
77-
initializer_path="$rails_root/config/initializers/react_on_rails.rb"
78-
if [ ! -f "$initializer_path" ]; then
79-
return 0
80-
fi
81-
82-
# Check if auto-pack generation is configured (ignore comments)
83-
# Look for uncommented config.auto_load_bundle or config.components_subdirectory
84-
if ! grep -q "^[[:space:]]*config\.auto_load_bundle[[:space:]]*=" "$initializer_path" && \
85-
! grep -q "^[[:space:]]*config\.components_subdirectory[[:space:]]*=" "$initializer_path"; then
86-
return 0
87-
fi
88-
89-
echo "📦 Generating React on Rails packs..."
90-
91-
# Check if bundle is available
92-
if ! command -v bundle >/dev/null 2>&1; then
93-
return 0
94-
fi
95-
96-
# Change to Rails root
97-
cd "$rails_root" || return 1
98-
99-
# Check if rake task exists
100-
if ! bundle exec rails -T | grep -q "react_on_rails:generate_packs"; then
101-
return 0
102-
fi
103-
104-
# Skip validation during precompile hook execution
105-
# The hook runs early in the build process and doesn't need package version validation
106-
export REACT_ON_RAILS_SKIP_VALIDATION=true
107-
108-
# Run pack generation
109-
if bundle exec rails react_on_rails:generate_packs; then
110-
echo "✅ Pack generation completed successfully"
111-
return 0
112-
else
113-
echo "❌ Pack generation failed" >&2
114-
exit 1
115-
fi
116-
}
28+
def generate_packs_if_needed
29+
rails_root = find_rails_root
30+
return unless rails_root
31+
32+
initializer_path = File.join(rails_root, "config", "initializers", "react_on_rails.rb")
33+
return unless File.exist?(initializer_path)
34+
35+
# Check if auto-pack generation is configured
36+
initializer_content = File.read(initializer_path)
37+
return unless initializer_content.match?(/^\s*config\.auto_load_bundle\s*=/) ||
38+
initializer_content.match?(/^\s*config\.components_subdirectory\s*=/)
39+
40+
puts "📦 Generating React on Rails packs..."
41+
42+
Dir.chdir(rails_root) do
43+
# Skip validation during precompile hook execution
44+
ENV["REACT_ON_RAILS_SKIP_VALIDATION"] = "true"
45+
46+
# Run pack generation
47+
system("bundle", "exec", "rails", "react_on_rails:generate_packs", exception: true)
48+
puts "✅ Pack generation completed successfully"
49+
end
50+
rescue Errno::ENOENT => e
51+
warn "⚠️ Warning: #{e.message}"
52+
rescue StandardError => e
53+
warn "❌ Pack generation failed: #{e.message}"
54+
exit 1
55+
end
11756

11857
# Main execution
119-
build_rescript_if_needed
12058
generate_packs_if_needed
121-
122-
exit 0
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
# Shakapacker precompile hook for React on Rails - Shared Implementation
5+
#
6+
# This is the shared implementation used by both test dummy apps:
7+
# - spec/dummy/bin/shakapacker-precompile-hook
8+
# - react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook
9+
#
10+
# This script runs before webpack compilation to:
11+
# 1. Build ReScript files (if configured)
12+
# 2. Generate pack files for auto-bundled components
13+
#
14+
# See: https://github.com/shakacode/shakapacker/blob/main/docs/precompile_hook.md
15+
16+
require "fileutils"
17+
require "json"
18+
19+
# Find Rails root by walking upward looking for config/environment.rb
20+
def find_rails_root
21+
dir = Dir.pwd
22+
while dir != "/"
23+
return dir if File.exist?(File.join(dir, "config", "environment.rb"))
24+
25+
dir = File.dirname(dir)
26+
end
27+
nil
28+
end
29+
30+
# Build ReScript if needed
31+
# rubocop:disable Metrics/CyclomaticComplexity
32+
def build_rescript_if_needed
33+
rails_root = find_rails_root
34+
unless rails_root
35+
warn "⚠️ Warning: Could not find Rails root. Skipping ReScript build."
36+
return
37+
end
38+
39+
# Check for both old (bsconfig.json) and new (rescript.json) config files
40+
return unless File.exist?(File.join(rails_root, "bsconfig.json")) ||
41+
File.exist?(File.join(rails_root, "rescript.json"))
42+
43+
puts "🔧 Building ReScript..."
44+
45+
# Validate that build:rescript script exists in package.json
46+
package_json_path = File.join(rails_root, "package.json")
47+
unless File.exist?(package_json_path)
48+
warn "⚠️ Warning: package.json not found. Skipping ReScript build."
49+
return
50+
end
51+
52+
package_json = JSON.parse(File.read(package_json_path))
53+
unless package_json.dig("scripts", "build:rescript")
54+
warn "⚠️ Warning: ReScript config found but no build:rescript script in package.json"
55+
warn " Add a build:rescript script to your package.json to enable ReScript builds"
56+
return
57+
end
58+
59+
Dir.chdir(rails_root) do
60+
# Cross-platform package manager detection
61+
if system("which yarn > /dev/null 2>&1")
62+
system("yarn", "build:rescript", exception: true)
63+
elsif system("which npm > /dev/null 2>&1")
64+
system("npm", "run", "build:rescript", exception: true)
65+
else
66+
warn "⚠️ Warning: Neither yarn nor npm found. Skipping ReScript build."
67+
return
68+
end
69+
70+
puts "✅ ReScript build completed successfully"
71+
end
72+
rescue StandardError => e
73+
warn "❌ ReScript build failed: #{e.message}"
74+
exit 1
75+
end
76+
# rubocop:enable Metrics/CyclomaticComplexity
77+
78+
# Generate React on Rails packs if needed
79+
def generate_packs_if_needed
80+
rails_root = find_rails_root
81+
return unless rails_root
82+
83+
initializer_path = File.join(rails_root, "config", "initializers", "react_on_rails.rb")
84+
return unless File.exist?(initializer_path)
85+
86+
# Check if auto-pack generation is configured
87+
initializer_content = File.read(initializer_path)
88+
return unless initializer_content.match?(/^\s*config\.auto_load_bundle\s*=/) ||
89+
initializer_content.match?(/^\s*config\.components_subdirectory\s*=/)
90+
91+
puts "📦 Generating React on Rails packs..."
92+
93+
Dir.chdir(rails_root) do
94+
# Skip validation during precompile hook execution
95+
ENV["REACT_ON_RAILS_SKIP_VALIDATION"] = "true"
96+
97+
# Run pack generation
98+
system("bundle", "exec", "rails", "react_on_rails:generate_packs", exception: true)
99+
puts "✅ Pack generation completed successfully"
100+
end
101+
rescue Errno::ENOENT => e
102+
warn "⚠️ Warning: #{e.message}"
103+
rescue StandardError => e
104+
warn "❌ Pack generation failed: #{e.message}"
105+
exit 1
106+
end
107+
108+
# Main execution (only if run directly, not when required)
109+
if __FILE__ == $PROGRAM_NAME
110+
build_rescript_if_needed
111+
generate_packs_if_needed
112+
end

0 commit comments

Comments
 (0)