Skip to content

Commit 1c37907

Browse files
authored
Skip generate_packs when shakapacker precompile hook configured (#1861)
Skip generate_packs when shakapacker precompile hook configured (#1861) Why Prevent duplicate pack generation when shakapacker handles it via hooks. Summary Detect shakapacker precompile hooks and skip react_on_rails:generate_packs accordingly. Support both direct commands and script file detection. Key improvements - Auto-detect hooks in shakapacker.yml (symbol/string keys, script files) - Display hook value in logs for transparency - Fail-safe approach ensures packs generate if detection fails Impact - Existing: No change unless shakapacker hook is configured - New: Automatic skip when hook contains react_on_rails:generate_packs Risks None. Detection failures default to running generate_packs (safe).
1 parent 7d61170 commit 1c37907

File tree

5 files changed

+410
-124
lines changed

5 files changed

+410
-124
lines changed

lib/react_on_rails/configuration.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,14 @@ 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+
if ReactOnRails::PackerUtils.shakapacker_precompile_hook_configured?
243+
hook_value = ReactOnRails::PackerUtils.shakapacker_precompile_hook_value
244+
puts "Skipping react_on_rails:generate_packs (configured in shakapacker precompile hook: #{hook_value})"
245+
else
246+
Rake::Task["react_on_rails:generate_packs"].invoke
247+
end
248+
242249
Rake::Task["react_on_rails:assets:webpack"].invoke
243250

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

lib/react_on_rails/dev/pack_generator.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,36 @@
55

66
module ReactOnRails
77
module Dev
8+
# PackGenerator triggers the generation of React on Rails packs
9+
#
10+
# Design decisions:
11+
# 1. Why trigger via Rake task instead of direct Ruby code?
12+
# - The actual pack generation logic lives in lib/react_on_rails/packs_generator.rb
13+
# - The rake task (lib/tasks/generate_packs.rake) provides a stable, documented interface
14+
# - This allows the implementation to evolve without breaking bin/dev
15+
# - Users can also run the task directly: `rake react_on_rails:generate_packs`
16+
#
17+
# 2. Why two execution strategies (direct vs bundle exec)?
18+
# - Direct Rake execution: Faster when already in Bundler/Rails context (bin/dev)
19+
# - Bundle exec fallback: Required when called outside Rails context
20+
# - This optimization avoids subprocess overhead in the common case
21+
#
22+
# 3. Why is the class named "PackGenerator" when it delegates?
23+
# - It's a semantic wrapper around pack generation for the dev workflow
24+
# - Provides a clean API for bin/dev without exposing Rake internals
25+
# - Handles hook detection, error handling, and output formatting
826
class PackGenerator
927
class << self
1028
def generate(verbose: false)
29+
# Skip if shakapacker has a precompile hook configured
30+
if ReactOnRails::PackerUtils.shakapacker_precompile_hook_configured?
31+
if verbose
32+
hook_value = ReactOnRails::PackerUtils.shakapacker_precompile_hook_value
33+
puts "⏭️ Skipping pack generation (handled by shakapacker precompile hook: #{hook_value})"
34+
end
35+
return
36+
end
37+
1138
if verbose
1239
puts "📦 Generating React on Rails packs..."
1340
success = run_pack_generation

lib/react_on_rails/packer_utils.rb

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,5 +166,78 @@ 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 twice
172+
#
173+
# Returns false if detection fails for any reason (missing shakapacker, malformed config, etc.)
174+
# to ensure generate_packs runs rather than being incorrectly skipped
175+
#
176+
# Note: Currently checks a single hook value. Future enhancement will support hook lists
177+
# to allow prepending/appending multiple commands. See related Shakapacker issue for details.
178+
def self.shakapacker_precompile_hook_configured?
179+
return false unless defined?(::Shakapacker)
180+
181+
hook_value = extract_precompile_hook
182+
return false if hook_value.nil?
183+
184+
hook_contains_generate_packs?(hook_value)
185+
rescue StandardError => e
186+
# Swallow errors during hook detection to fail safe - if we can't detect the hook,
187+
# we should run generate_packs rather than skip it incorrectly.
188+
# Possible errors: NoMethodError (config method missing), TypeError (unexpected data structure),
189+
# or errors from shakapacker's internal implementation changes
190+
warn "Warning: Unable to detect shakapacker precompile hook: #{e.message}" if ENV["DEBUG"]
191+
false
192+
end
193+
194+
def self.extract_precompile_hook
195+
# Access config data using private :data method since there's no public API
196+
# to access the raw configuration hash needed for hook detection
197+
config_data = ::Shakapacker.config.send(:data)
198+
199+
# 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")
202+
end
203+
204+
def self.hook_contains_generate_packs?(hook_value)
205+
# The hook value can be either:
206+
# 1. A direct command containing the rake task
207+
# 2. A path to a script file that needs to be read
208+
return false if hook_value.blank?
209+
210+
# Check if it's a direct command first
211+
return true if hook_value.to_s.match?(/\breact_on_rails:generate_packs\b/)
212+
213+
# Check if it's a script file path
214+
script_path = resolve_hook_script_path(hook_value)
215+
return false unless script_path && File.exist?(script_path)
216+
217+
# Read and check script contents
218+
script_contents = File.read(script_path)
219+
script_contents.match?(/\breact_on_rails:generate_packs\b/)
220+
rescue StandardError
221+
# If we can't read the script, assume it doesn't contain generate_packs
222+
false
223+
end
224+
225+
def self.resolve_hook_script_path(hook_value)
226+
# Hook value might be a script path relative to Rails root
227+
return nil unless defined?(Rails) && Rails.respond_to?(:root)
228+
229+
potential_path = Rails.root.join(hook_value.to_s.strip)
230+
potential_path if potential_path.file?
231+
end
232+
233+
# Returns the configured precompile hook value for logging/debugging
234+
# Returns nil if no hook is configured
235+
def self.shakapacker_precompile_hook_value
236+
return nil unless defined?(::Shakapacker)
237+
238+
extract_precompile_hook
239+
rescue StandardError
240+
nil
241+
end
169242
end
170243
end

0 commit comments

Comments
 (0)