Skip to content

Commit ca65f32

Browse files
justin808claude
andcommitted
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]>
1 parent a59bbb7 commit ca65f32

File tree

9 files changed

+107
-19
lines changed

9 files changed

+107
-19
lines changed

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
49+
File.chmod(0o755, "bin/shakapacker-precompile-hook") if File.exist?("bin/shakapacker-precompile-hook")
4650
end
4751

4852
def copy_js_bundle_files
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
require_relative "../config/environment"
12+
13+
begin
14+
puts Rainbow("🔄 Running React on Rails precompile hook...").cyan
15+
ReactOnRails::PacksGenerator.instance.generate_packs_if_stale
16+
rescue StandardError => e
17+
warn Rainbow("❌ Error in precompile hook: #{e.message}").red
18+
warn e.backtrace.first(5).join("\n")
19+
exit 1
20+
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. The hook command will be
47+
# validated to ensure it points to a file within the project root.
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/configuration.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,11 @@ def adjust_precompile_task
238238
raise(ReactOnRails::Error, compile_command_conflict_message) if ReactOnRails::PackerUtils.precompile?
239239

240240
precompile_tasks = lambda {
241-
Rake::Task["react_on_rails:generate_packs"].invoke
241+
# Skip generate_packs if shakapacker has a precompile hook configured
242+
unless ReactOnRails::PackerUtils.shakapacker_precompile_hook_configured?
243+
Rake::Task["react_on_rails:generate_packs"].invoke
244+
end
245+
242246
Rake::Task["react_on_rails:assets:webpack"].invoke
243247

244248
# VERSIONS is per the shakacode/shakapacker clean method definition.

lib/react_on_rails/dev/pack_generator.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ module Dev
77
class PackGenerator
88
class << self
99
def generate(verbose: false)
10+
# Skip if shakapacker has a precompile hook configured
11+
if ReactOnRails::PackerUtils.shakapacker_precompile_hook_configured?
12+
puts "⏭️ Skipping pack generation (handled by shakapacker precompile hook)" if verbose
13+
return
14+
end
15+
1016
if verbose
1117
puts "📦 Generating React on Rails packs..."
1218
success = system "bundle exec rake react_on_rails:generate_packs"

lib/react_on_rails/dev/server_manager.rb

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

279+
features = [
280+
"Precompiling assets with production optimizations",
281+
"Running Rails server on port 3001",
282+
"No HMR (Hot Module Replacement)",
283+
"CSS extracted to separate files (no FOUC)"
284+
]
285+
286+
# NOTE: Pack generation happens automatically during assets:precompile
287+
# either via precompile hook or via the configuration.rb adjust_precompile_task
288+
279289
print_procfile_info(procfile, route: route)
280290
print_server_info(
281291
"🏭 Starting production-like development server...",
282-
[
283-
"Generating React on Rails packs",
284-
"Precompiling assets with production optimizations",
285-
"Running Rails server on port 3001",
286-
"No HMR (Hot Module Replacement)",
287-
"CSS extracted to separate files (no FOUC)"
288-
],
292+
features,
289293
3001,
290294
route: route
291295
)
@@ -404,15 +408,22 @@ def run_production_like(_verbose: false, route: nil, rails_env: nil)
404408

405409
def run_static_development(procfile, verbose: false, route: nil)
406410
print_procfile_info(procfile, route: route)
411+
412+
features = [
413+
"Using shakapacker --watch (no HMR)",
414+
"CSS extracted to separate files (no FOUC)",
415+
"Development environment (source maps, faster builds)",
416+
"Auto-recompiles on file changes"
417+
]
418+
419+
# Add pack generation info if not using precompile hook
420+
unless ReactOnRails::PackerUtils.shakapacker_precompile_hook_configured?
421+
features.unshift("Generating React on Rails packs")
422+
end
423+
407424
print_server_info(
408425
"⚡ Starting development server with static assets...",
409-
[
410-
"Generating React on Rails packs",
411-
"Using shakapacker --watch (no HMR)",
412-
"CSS extracted to separate files (no FOUC)",
413-
"Development environment (source maps, faster builds)",
414-
"Auto-recompiles on file changes"
415-
],
426+
features,
416427
route: route
417428
)
418429

lib/react_on_rails/packer_utils.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,5 +166,18 @@ def self.raise_shakapacker_version_incompatible_for_basic_pack_generation
166166

167167
raise ReactOnRails::Error, msg
168168
end
169+
170+
# Check if shakapacker.yml has a precompile_hook configured
171+
# This prevents react_on_rails from running generate_packs redundantly
172+
def self.shakapacker_precompile_hook_configured?
173+
return false unless defined?(::Shakapacker)
174+
175+
config_data = ::Shakapacker.config.send(:data)
176+
hook = config_data[:precompile_hook]
177+
178+
hook.present?
179+
rescue StandardError
180+
false
181+
end
169182
end
170183
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
require_relative "../config/environment"
12+
13+
begin
14+
puts Rainbow("🔄 Running React on Rails precompile hook...").cyan
15+
ReactOnRails::PacksGenerator.instance.generate_packs_if_stale
16+
rescue StandardError => e
17+
warn Rainbow("❌ Error in precompile hook: #{e.message}").red
18+
warn e.backtrace.first(5).join("\n")
19+
exit 1
20+
end

spec/dummy/config/shakapacker.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ default: &default
1717
cache_manifest: false
1818
nested_entries: true
1919

20+
# Hook to run before webpack compilation (e.g., for generating dynamic entry points)
21+
# SECURITY: Only reference trusted scripts within your project. The hook command will be
22+
# validated to ensure it points to a file within the project root.
23+
precompile_hook: 'bin/shakapacker-precompile-hook'
24+
2025
development:
2126
<<: *default
2227
# Turn this to true if you want to use the rails/shakapacker check that the test

0 commit comments

Comments
 (0)