Skip to content

Commit 73dacca

Browse files
justin808claude
andauthored
Add Shakapacker precompile hook template for generator (#1916)
* Add Shakapacker precompile hook support for auto pack generation This change leverages Shakapacker's new precompile_hook feature to automatically generate packs before webpack compilation, replacing the need to manually modify assets:precompile tasks or bin/dev scripts. Changes: - Add bin/shakapacker-precompile-hook script to templates and spec/dummy - Update shakapacker.yml to configure precompile_hook - Add PackerUtils.shakapacker_precompile_hook_configured? method - Skip generate_packs in configuration and PackGenerator when hook configured - Update generator to copy hook script and make it executable The precompile hook runs before webpack compilation and is properly validated by Shakapacker to ensure it points to a file within the project root. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Fix: Add execute permission to shakapacker-precompile-hook template The template file needs execute permission to pass RuboCop's Lint/ScriptPermission check. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Fix: Skip validation in precompile hook to prevent generator failures The shakapacker-precompile-hook was causing generator failures because it loads the Rails environment (which triggers version validation) before the react-on-rails npm package is installed during initial setup. This fix adds ENV["REACT_ON_RAILS_SKIP_VALIDATION"] = "true" at the start of the hook script to skip version validation, since the hook runs as part of the build process and doesn't need package version validation. Fixes CI failures in generator tests where the hook was executed during `rails generate react_on_rails:install` before packages were installed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Fix: Disable yarn caching in examples workflow to prevent V8 crash The examples.yml workflow was experiencing Node.js V8 crashes during yarn cache dir commands, similar to the issue fixed in commit a1c71ea. This commit applies the same fix - disabling yarn caching until the upstream V8 issue is resolved. Error observed: ``` # Fatal error in , line 0 # unreachable code ``` Tracking: actions/setup-node#1028 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Fix: Remove duplicate precompile_hook key in shakapacker.yml The spec/dummy/config/shakapacker.yml had two `precompile_hook` entries which caused a YAML parsing error: YAMLException: duplicated mapping key (31:3) Removed the first instance at line 14, keeping the one with better documentation at line 31. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Fix: Correct misleading security comment in shakapacker.yml The comment claimed "The hook command will be validated to ensure it points to a file within the project root" but no such validation exists in the codebase. Updated the comment to accurately reflect that users must ensure the hook path points to a trusted file they control. This removes the false promise of automatic validation and places the security responsibility appropriately on the user configuring the hook. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Fix: Correct precompile_hook detection to use top-level config key The master's sophisticated hook detection was looking for the hook at `:hooks/:precompile` but shakapacker.yml uses `precompile_hook` at the top level. Updated `extract_precompile_hook` to look in the correct location. Also updated specs to match the sophisticated implementation that checks if the hook actually contains the generate_packs rake task, rather than just checking for any hook presence. Fixes failing rspec-package-tests in CI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]>
1 parent bedee51 commit 73dacca

File tree

9 files changed

+246
-292
lines changed

9 files changed

+246
-292
lines changed

.github/workflows/examples.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,10 @@ jobs:
6666
uses: actions/setup-node@v4
6767
with:
6868
node-version: 20
69-
cache: yarn
70-
cache-dependency-path: '**/yarn.lock'
69+
# TODO: Re-enable yarn caching once Node.js V8 cache crash is fixed
70+
# Tracking: https://github.com/actions/setup-node/issues/1028
71+
# cache: yarn
72+
# cache-dependency-path: '**/yarn.lock'
7173
- name: Print system information
7274
run: |
7375
echo "Linux release: "; cat /etc/issue

lib/generators/react_on_rails/base_generator.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,16 @@ def copy_base_files
3737
app/views/layouts/hello_world.html.erb
3838
Procfile.dev
3939
Procfile.dev-static-assets
40-
Procfile.dev-prod-assets]
40+
Procfile.dev-prod-assets
41+
bin/shakapacker-precompile-hook]
4142
base_templates = %w[config/initializers/react_on_rails.rb]
4243
base_files.each { |file| copy_file("#{base_path}#{file}", file) }
4344
base_templates.each do |file|
4445
template("#{base_path}/#{file}.tt", file)
4546
end
47+
48+
# Make the hook script executable (copy_file guarantees it exists)
49+
File.chmod(0o755, File.join(destination_root, "bin/shakapacker-precompile-hook"))
4650
end
4751

4852
def copy_js_bundle_files
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
# Shakapacker precompile hook for React on Rails
5+
#
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:
9+
# precompile_hook: 'bin/shakapacker-precompile-hook'
10+
#
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

lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ default: &default
4242
# Raises an error if there is a mismatch in the shakapacker gem and npm package being used
4343
ensure_consistent_versioning: true
4444

45+
# Hook to run before webpack compilation (e.g., for generating dynamic entry points)
46+
# SECURITY: Only reference trusted scripts within your project. Ensure the hook path
47+
# points to a file within the project root that you control.
48+
precompile_hook: 'bin/shakapacker-precompile-hook'
49+
4550
# Select whether the compiler will use SHA digest ('digest' option) or most recent modified timestamp ('mtime') to determine freshness
4651
compiler_strategy: digest
4752

lib/react_on_rails/dev/server_manager.rb

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ def help_mode_details
248248
<<~MODES
249249
#{Rainbow('🔥 HMR Development mode (default)').cyan.bold} - #{Rainbow('Procfile.dev').green}:
250250
#{Rainbow('•').yellow} #{Rainbow('Hot Module Replacement (HMR) enabled').white}
251-
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation before Procfile start').white}
251+
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation (via precompile hook or bin/dev)').white}
252252
#{Rainbow('•').yellow} #{Rainbow('Webpack dev server for fast recompilation').white}
253253
#{Rainbow('•').yellow} #{Rainbow('Source maps for debugging').white}
254254
#{Rainbow('•').yellow} #{Rainbow('May have Flash of Unstyled Content (FOUC)').white}
@@ -257,15 +257,15 @@ def help_mode_details
257257
258258
#{Rainbow('📦 Static development mode').cyan.bold} - #{Rainbow('Procfile.dev-static-assets').green}:
259259
#{Rainbow('•').yellow} #{Rainbow('No HMR (static assets with auto-recompilation)').white}
260-
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation before Procfile start').white}
260+
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation (via precompile hook or bin/dev)').white}
261261
#{Rainbow('•').yellow} #{Rainbow('Webpack watch mode for auto-recompilation').white}
262262
#{Rainbow('•').yellow} #{Rainbow('CSS extracted to separate files (no FOUC)').white}
263263
#{Rainbow('•').yellow} #{Rainbow('Development environment (faster builds than production)').white}
264264
#{Rainbow('•').yellow} #{Rainbow('Source maps for debugging').white}
265265
#{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3000/<route>').cyan.underline}
266266
267267
#{Rainbow('🏭 Production-assets mode').cyan.bold} - #{Rainbow('Procfile.dev-prod-assets').green}:
268-
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation before Procfile start').white}
268+
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation (via precompile hook or assets:precompile)').white}
269269
#{Rainbow('•').yellow} #{Rainbow('Asset precompilation with NODE_ENV=production (webpack optimizations)').white}
270270
#{Rainbow('•').yellow} #{Rainbow('RAILS_ENV=development by default for assets:precompile (avoids credentials)').white}
271271
#{Rainbow('•').yellow} #{Rainbow('Use --rails-env=production for assets:precompile only (not server processes)').white}
@@ -281,16 +281,20 @@ def help_mode_details
281281
def run_production_like(_verbose: false, route: nil, rails_env: nil)
282282
procfile = "Procfile.dev-prod-assets"
283283

284+
features = [
285+
"Precompiling assets with production optimizations",
286+
"Running Rails server on port 3001",
287+
"No HMR (Hot Module Replacement)",
288+
"CSS extracted to separate files (no FOUC)"
289+
]
290+
291+
# NOTE: Pack generation happens automatically during assets:precompile
292+
# either via precompile hook or via the configuration.rb adjust_precompile_task
293+
284294
print_procfile_info(procfile, route: route)
285295
print_server_info(
286296
"🏭 Starting production-like development server...",
287-
[
288-
"Generating React on Rails packs",
289-
"Precompiling assets with production optimizations",
290-
"Running Rails server on port 3001",
291-
"No HMR (Hot Module Replacement)",
292-
"CSS extracted to separate files (no FOUC)"
293-
],
297+
features,
294298
3001,
295299
route: route
296300
)
@@ -409,15 +413,22 @@ def run_production_like(_verbose: false, route: nil, rails_env: nil)
409413

410414
def run_static_development(procfile, verbose: false, route: nil)
411415
print_procfile_info(procfile, route: route)
416+
417+
features = [
418+
"Using shakapacker --watch (no HMR)",
419+
"CSS extracted to separate files (no FOUC)",
420+
"Development environment (source maps, faster builds)",
421+
"Auto-recompiles on file changes"
422+
]
423+
424+
# Add pack generation info if not using precompile hook
425+
unless ReactOnRails::PackerUtils.shakapacker_precompile_hook_configured?
426+
features.unshift("Generating React on Rails packs")
427+
end
428+
412429
print_server_info(
413430
"⚡ Starting development server with static assets...",
414-
[
415-
"Generating React on Rails packs",
416-
"Using shakapacker --watch (no HMR)",
417-
"CSS extracted to separate files (no FOUC)",
418-
"Development environment (source maps, faster builds)",
419-
"Auto-recompiles on file changes"
420-
],
431+
features,
421432
route: route
422433
)
423434

lib/react_on_rails/packer_utils.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,8 @@ def self.extract_precompile_hook
197197
config_data = ::Shakapacker.config.send(:data)
198198

199199
# Try symbol keys first (Shakapacker's internal format), then fall back to string keys
200-
# Note: Currently only one hook value is supported, but this will change to support lists
201-
config_data&.dig(:hooks, :precompile) || config_data&.dig("hooks", "precompile")
200+
# The key is 'precompile_hook' at the top level of the config
201+
config_data&.[](:precompile_hook) || config_data&.[]("precompile_hook")
202202
end
203203

204204
def self.hook_contains_generate_packs?(hook_value)

spec/dummy/config/shakapacker.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ default: &default
88
# Using SWC for faster JavaScript transpilation (20x faster than Babel)
99
javascript_transpiler: swc
1010

11-
# Hook to run before compilation (e.g., for ReScript builds, pack generation)
12-
# See: https://github.com/shakacode/shakapacker/blob/main/docs/precompile_hook.md
13-
precompile_hook: bin/shakapacker-precompile-hook
14-
1511
cache_path: tmp/cache/shakapacker
1612
webpack_compile_output: false
1713
ensure_consistent_versioning: true
@@ -24,6 +20,11 @@ default: &default
2420
cache_manifest: false
2521
nested_entries: true
2622

23+
# Hook to run before webpack compilation (e.g., for generating dynamic entry points)
24+
# SECURITY: Only reference trusted scripts within your project. Ensure the hook path
25+
# points to a file within the project root that you control.
26+
precompile_hook: 'bin/shakapacker-precompile-hook'
27+
2728
development:
2829
<<: *default
2930
# Turn this to true if you want to use the rails/shakapacker check that the test

0 commit comments

Comments
 (0)