Skip to content

Commit 8ef957c

Browse files
justin808claude
andcommitted
Replace Ruby precompile hooks with shared bash script
Convert precompile hooks from Ruby to bash and DRY them up: **Why Bash Over Ruby:** - Simpler: No Ruby stdlib dependencies - Faster: No Ruby interpreter startup overhead - Standard: Most build hooks are shell scripts - Cross-platform: Works everywhere (sh/bash universal) - Fewer dependencies: Doesn't require Ruby loaded **Changes:** - Create shared bash script in generator templates - Fix ReScript build to run from Rails root (cd into rails_root) - Fix pack generation to run from Rails root - Use proper path resolution with Rails root for all file checks - Replace Ruby scripts in both dummy apps with bash version - All three copies identical (generator template + 2 dummy apps) **Script Features:** - Finds Rails root by walking up directory tree - Detects ReScript config (bsconfig.json or rescript.json) - Runs ReScript builds from correct directory - Detects auto_load_bundle/components_subdirectory config - Generates packs when configured - Cross-platform package manager detection (yarn/npm) - Proper error handling and exit codes - Sets REACT_ON_RAILS_SKIP_VALIDATION for build context **Testing:** - Shellcheck passes with no warnings - Script executes successfully in non-pro dummy app - ReScript builds complete successfully - Pack generation runs successfully - All files have trailing newlines 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent dbd8cfe commit 8ef957c

File tree

3 files changed

+322
-198
lines changed

3 files changed

+322
-198
lines changed
Lines changed: 118 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,122 @@
1-
#!/usr/bin/env ruby
2-
# frozen_string_literal: true
3-
1+
#!/bin/sh
42
# Shakapacker precompile hook for React on Rails
53
#
6-
# This script runs before webpack compilation to generate pack files
7-
# for auto-bundled components. It's called automatically by Shakapacker
8-
# when configured in config/shakapacker.yml:
4+
# This script runs before webpack compilation to:
5+
# 1. Build ReScript files (if configured)
6+
# 2. Generate pack files for auto-bundled components
7+
#
8+
# It's called automatically by Shakapacker when configured in config/shakapacker.yml:
99
# precompile_hook: 'bin/shakapacker-precompile-hook'
1010
#
11-
# Emoji Scheme:
12-
# 🔄 = Running/in-progress
13-
# ✅ = Success
14-
# ❌ = Error
15-
16-
# Skip validation during precompile hook execution
17-
# The hook runs early in the build process, potentially before full Rails initialization,
18-
# and doesn't need package version validation since it's part of the build itself
19-
ENV["REACT_ON_RAILS_SKIP_VALIDATION"] = "true"
20-
21-
require_relative "../config/environment"
22-
23-
begin
24-
puts Rainbow("🔄 Running React on Rails precompile hook...").cyan
25-
ReactOnRails::PacksGenerator.instance.generate_packs_if_stale
26-
rescue StandardError => e
27-
warn Rainbow("❌ Error in precompile hook: #{e.message}").red
28-
warn e.backtrace.first(5).join("\n")
29-
exit 1
30-
end
11+
# See: https://github.com/shakacode/shakapacker/blob/main/docs/precompile_hook.md
12+
13+
set -e
14+
15+
# 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..."
42+
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+
}
68+
69+
# 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+
}
117+
118+
# Main execution
119+
build_rescript_if_needed
120+
generate_packs_if_needed
121+
122+
exit 0
Lines changed: 101 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,122 @@
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.
1+
#!/bin/sh
2+
# Shakapacker precompile hook for React on Rails
3+
#
4+
# This script runs before webpack compilation to:
5+
# 1. Build ReScript files (if configured)
6+
# 2. Generate pack files for auto-bundled components
7+
#
8+
# It's called automatically by Shakapacker when configured in config/shakapacker.yml:
9+
# precompile_hook: 'bin/shakapacker-precompile-hook'
10+
#
611
# See: https://github.com/shakacode/shakapacker/blob/main/docs/precompile_hook.md
712

8-
require "fileutils"
13+
set -e
914

1015
# 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
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+
}
2227

2328
# Build ReScript if needed
24-
def build_rescript_if_needed
25-
# Find Rails root directory
26-
rails_root = find_rails_root
27-
unless rails_root
28-
warn "⚠️ Warning: Could not find Rails root. Skipping ReScript build."
29-
return
30-
end
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
3135

3236
# Check for both old (bsconfig.json) and new (rescript.json) config files
33-
bsconfig_path = File.join(rails_root, "bsconfig.json")
34-
rescript_config_path = File.join(rails_root, "rescript.json")
35-
return unless File.exist?(bsconfig_path) || File.exist?(rescript_config_path)
37+
if [ ! -f "$rails_root/bsconfig.json" ] && [ ! -f "$rails_root/rescript.json" ]; then
38+
return 0
39+
fi
40+
41+
echo "🔧 Building ReScript..."
3642

37-
puts "🔧 Building ReScript..."
43+
# Change to Rails root to run build commands
44+
cd "$rails_root" || return 1
3845

3946
# Cross-platform package manager detection
40-
yarn_available = system("yarn", "--version", out: File::NULL, err: File::NULL)
41-
npm_available = system("npm", "--version", out: File::NULL, err: File::NULL)
42-
43-
# Run build command from Rails root directory
44-
success = Dir.chdir(rails_root) do
45-
if yarn_available
46-
system("yarn", "build:rescript")
47-
elsif npm_available
48-
system("npm", "run", "build:rescript")
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
4951
else
50-
warn "⚠️ Warning: Neither yarn nor npm found. Skipping ReScript build."
51-
return false
52-
end
53-
end
54-
55-
if success
56-
puts "✅ ReScript build completed successfully"
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
5763
else
58-
warn "❌ ReScript build failed"
59-
exit 1
60-
end
61-
end
64+
echo "⚠️ Warning: Neither yarn nor npm found. Skipping ReScript build."
65+
return 0
66+
fi
67+
}
6268

6369
# Generate React on Rails packs if needed
64-
def generate_packs_if_needed
65-
# Find Rails root directory
66-
rails_root = find_rails_root
67-
return unless rails_root
70+
generate_packs_if_needed() {
71+
rails_root=$(find_rails_root)
72+
if [ -z "$rails_root" ]; then
73+
return 0
74+
fi
6875

6976
# Check if React on Rails initializer exists
70-
initializer_path = File.join(rails_root, "config", "initializers", "react_on_rails.rb")
71-
return unless File.exist?(initializer_path)
72-
73-
# Check if auto-pack generation is configured (match actual config assignments, not comments)
74-
config_file = File.read(initializer_path)
75-
# Match uncommented configuration lines only (lines not starting with #)
76-
has_auto_load = config_file =~ /^\s*(?!#).*config\.auto_load_bundle\s*=/
77-
has_components_subdir = config_file =~ /^\s*(?!#).*config\.components_subdirectory\s*=/
78-
return unless has_auto_load || has_components_subdir
79-
80-
puts "📦 Generating React on Rails packs..."
81-
82-
# Cross-platform bundle availability check
83-
bundle_available = system("bundle", "--version", out: File::NULL, err: File::NULL)
84-
return unless bundle_available
85-
86-
# Check if rake task exists (use array form for security)
87-
task_list = IO.popen(["bundle", "exec", "rails", "-T"], err: %i[child out], &:read)
88-
return unless task_list.include?("react_on_rails:generate_packs")
89-
90-
# Use array form for better cross-platform support
91-
success = system("bundle", "exec", "rails", "react_on_rails:generate_packs")
92-
93-
if success
94-
puts "✅ Pack generation completed successfully"
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
95112
else
96-
warn "❌ Pack generation failed"
113+
echo "❌ Pack generation failed" >&2
97114
exit 1
98-
end
99-
end
115+
fi
116+
}
100117

101118
# Main execution
102-
begin
103-
build_rescript_if_needed
104-
generate_packs_if_needed
105-
106-
exit 0
107-
rescue StandardError => e
108-
warn "❌ Precompile hook failed: #{e.message}"
109-
warn e.backtrace.join("\n")
110-
exit 1
111-
end
119+
build_rescript_if_needed
120+
generate_packs_if_needed
121+
122+
exit 0

0 commit comments

Comments
 (0)