Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/generators/react_on_rails/base_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,16 @@ def copy_base_files
app/views/layouts/hello_world.html.erb
Procfile.dev
Procfile.dev-static-assets
Procfile.dev-prod-assets]
Procfile.dev-prod-assets
bin/shakapacker-precompile-hook]
base_templates = %w[config/initializers/react_on_rails.rb]
base_files.each { |file| copy_file("#{base_path}#{file}", file) }
base_templates.each do |file|
template("#{base_path}/#{file}.tt", file)
end

# Make the hook script executable
File.chmod(0o755, "bin/shakapacker-precompile-hook") if File.exist?("bin/shakapacker-precompile-hook")
end

def copy_js_bundle_files
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

# Shakapacker precompile hook for React on Rails
#
# This script runs before webpack compilation to generate pack files
# for auto-bundled components. It's called automatically by Shakapacker
# when configured in config/shakapacker.yml:
# precompile_hook: 'bin/shakapacker-precompile-hook'

require_relative "../config/environment"

begin
puts Rainbow("🔄 Running React on Rails precompile hook...").cyan
ReactOnRails::PacksGenerator.instance.generate_packs_if_stale
rescue StandardError => e
warn Rainbow("❌ Error in precompile hook: #{e.message}").red
warn e.backtrace.first(5).join("\n")
exit 1
end
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ default: &default
# Raises an error if there is a mismatch in the shakapacker gem and npm package being used
ensure_consistent_versioning: true

# Hook to run before webpack compilation (e.g., for generating dynamic entry points)
# SECURITY: Only reference trusted scripts within your project. The hook command will be
# validated to ensure it points to a file within the project root.
precompile_hook: 'bin/shakapacker-precompile-hook'

# Select whether the compiler will use SHA digest ('digest' option) or most recent modified timestamp ('mtime') to determine freshness
compiler_strategy: digest

Expand Down
6 changes: 5 additions & 1 deletion lib/react_on_rails/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,11 @@ def adjust_precompile_task
raise(ReactOnRails::Error, compile_command_conflict_message) if ReactOnRails::PackerUtils.precompile?

precompile_tasks = lambda {
Rake::Task["react_on_rails:generate_packs"].invoke
# Skip generate_packs if shakapacker has a precompile hook configured
unless ReactOnRails::PackerUtils.shakapacker_precompile_hook_configured?
Rake::Task["react_on_rails:generate_packs"].invoke
end

Rake::Task["react_on_rails:assets:webpack"].invoke

# VERSIONS is per the shakacode/shakapacker clean method definition.
Expand Down
6 changes: 6 additions & 0 deletions lib/react_on_rails/dev/pack_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ module Dev
class PackGenerator
class << self
def generate(verbose: false)
# Skip if shakapacker has a precompile hook configured
if ReactOnRails::PackerUtils.shakapacker_precompile_hook_configured?
puts "⏭️ Skipping pack generation (handled by shakapacker precompile hook)" if verbose
return
end

if verbose
puts "📦 Generating React on Rails packs..."
success = system "bundle exec rake react_on_rails:generate_packs"
Expand Down
45 changes: 28 additions & 17 deletions lib/react_on_rails/dev/server_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ def help_mode_details
<<~MODES
#{Rainbow('🔥 HMR Development mode (default)').cyan.bold} - #{Rainbow('Procfile.dev').green}:
#{Rainbow('•').yellow} #{Rainbow('Hot Module Replacement (HMR) enabled').white}
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation before Procfile start').white}
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation (via precompile hook or bin/dev)').white}
#{Rainbow('•').yellow} #{Rainbow('Webpack dev server for fast recompilation').white}
#{Rainbow('•').yellow} #{Rainbow('Source maps for debugging').white}
#{Rainbow('•').yellow} #{Rainbow('May have Flash of Unstyled Content (FOUC)').white}
Expand All @@ -252,15 +252,15 @@ def help_mode_details

#{Rainbow('📦 Static development mode').cyan.bold} - #{Rainbow('Procfile.dev-static-assets').green}:
#{Rainbow('•').yellow} #{Rainbow('No HMR (static assets with auto-recompilation)').white}
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation before Procfile start').white}
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation (via precompile hook or bin/dev)').white}
#{Rainbow('•').yellow} #{Rainbow('Webpack watch mode for auto-recompilation').white}
#{Rainbow('•').yellow} #{Rainbow('CSS extracted to separate files (no FOUC)').white}
#{Rainbow('•').yellow} #{Rainbow('Development environment (faster builds than production)').white}
#{Rainbow('•').yellow} #{Rainbow('Source maps for debugging').white}
#{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3000/<route>').cyan.underline}

#{Rainbow('🏭 Production-assets mode').cyan.bold} - #{Rainbow('Procfile.dev-prod-assets').green}:
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation before Procfile start').white}
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation (via precompile hook or assets:precompile)').white}
#{Rainbow('•').yellow} #{Rainbow('Asset precompilation with NODE_ENV=production (webpack optimizations)').white}
#{Rainbow('•').yellow} #{Rainbow('RAILS_ENV=development by default for assets:precompile (avoids credentials)').white}
#{Rainbow('•').yellow} #{Rainbow('Use --rails-env=production for assets:precompile only (not server processes)').white}
Expand All @@ -276,16 +276,20 @@ def help_mode_details
def run_production_like(_verbose: false, route: nil, rails_env: nil)
procfile = "Procfile.dev-prod-assets"

features = [
"Precompiling assets with production optimizations",
"Running Rails server on port 3001",
"No HMR (Hot Module Replacement)",
"CSS extracted to separate files (no FOUC)"
]

# NOTE: Pack generation happens automatically during assets:precompile
# either via precompile hook or via the configuration.rb adjust_precompile_task

print_procfile_info(procfile, route: route)
print_server_info(
"🏭 Starting production-like development server...",
[
"Generating React on Rails packs",
"Precompiling assets with production optimizations",
"Running Rails server on port 3001",
"No HMR (Hot Module Replacement)",
"CSS extracted to separate files (no FOUC)"
],
features,
3001,
route: route
)
Expand Down Expand Up @@ -404,15 +408,22 @@ def run_production_like(_verbose: false, route: nil, rails_env: nil)

def run_static_development(procfile, verbose: false, route: nil)
print_procfile_info(procfile, route: route)

features = [
"Using shakapacker --watch (no HMR)",
"CSS extracted to separate files (no FOUC)",
"Development environment (source maps, faster builds)",
"Auto-recompiles on file changes"
]

# Add pack generation info if not using precompile hook
unless ReactOnRails::PackerUtils.shakapacker_precompile_hook_configured?
features.unshift("Generating React on Rails packs")
end

print_server_info(
"⚡ Starting development server with static assets...",
[
"Generating React on Rails packs",
"Using shakapacker --watch (no HMR)",
"CSS extracted to separate files (no FOUC)",
"Development environment (source maps, faster builds)",
"Auto-recompiles on file changes"
],
features,
route: route
)

Expand Down
13 changes: 13 additions & 0 deletions lib/react_on_rails/packer_utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,5 +166,18 @@ def self.raise_shakapacker_version_incompatible_for_basic_pack_generation

raise ReactOnRails::Error, msg
end

# Check if shakapacker.yml has a precompile_hook configured
# This prevents react_on_rails from running generate_packs redundantly
def self.shakapacker_precompile_hook_configured?
return false unless defined?(::Shakapacker)

config_data = ::Shakapacker.config.send(:data)
hook = config_data[:precompile_hook]

hook.present?
rescue StandardError
false
end
end
end
20 changes: 20 additions & 0 deletions spec/dummy/bin/shakapacker-precompile-hook
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

# Shakapacker precompile hook for React on Rails
#
# This script runs before webpack compilation to generate pack files
# for auto-bundled components. It's called automatically by Shakapacker
# when configured in config/shakapacker.yml:
# precompile_hook: 'bin/shakapacker-precompile-hook'

require_relative "../config/environment"

begin
puts Rainbow("🔄 Running React on Rails precompile hook...").cyan
ReactOnRails::PacksGenerator.instance.generate_packs_if_stale
rescue StandardError => e
warn Rainbow("❌ Error in precompile hook: #{e.message}").red
warn e.backtrace.first(5).join("\n")
exit 1
end
5 changes: 5 additions & 0 deletions spec/dummy/config/shakapacker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ default: &default
cache_manifest: false
nested_entries: true

# Hook to run before webpack compilation (e.g., for generating dynamic entry points)
# SECURITY: Only reference trusted scripts within your project. The hook command will be
# validated to ensure it points to a file within the project root.
precompile_hook: 'bin/shakapacker-precompile-hook'

development:
<<: *default
# Turn this to true if you want to use the rails/shakapacker check that the test
Expand Down
Loading