Skip to content

Update bin/dev to Set SHAKAPACKER_SKIP_PRECOMPILE_HOOK After Running Hook #2091

@justin808

Description

@justin808

Update bin/dev to Set SHAKAPACKER_SKIP_PRECOMPILE_HOOK After Running Hook

Summary

When Shakapacker adds precompile_hook support (shakacode/shakapacker#849), React on Rails' bin/dev should be updated to:

  1. Run the precompile hook once before launching Procfile processes
  2. Set SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true so individual webpack processes don't re-run the hook

Problem

Currently, bin/dev launches Procfile processes directly via foreman/overmind. With the new Shakapacker precompile hook feature:

  • Each webpack process (bin/shakapacker, bin/shakapacker-dev-server) would independently run the precompile hook
  • This causes duplicate execution of expensive tasks (ReScript compilation, locale generation, etc.)
  • Race conditions when multiple processes try to generate the same files simultaneously

Current bin/dev Implementation

#!/usr/bin/env ruby
# frozen_string_literal: true

require "bundler/setup"
require "react_on_rails/dev"

# Main execution
ReactOnRails::Dev::ServerManager.run_from_command_line(ARGV)

Proposed Solution

Update ReactOnRails::Dev::ServerManager to:

  1. Check for and run the precompile hook once before launching Procfile
  2. Set the skip environment variable so Procfile processes don't re-run it
module ReactOnRails
  module Dev
    class ServerManager
      def self.run_from_command_line(args)
        # Run precompile hook once before launching processes
        run_precompile_hook_if_present

        # Set flag to prevent duplicate execution in spawned processes
        ENV['SHAKAPACKER_SKIP_PRECOMPILE_HOOK'] = 'true'

        # Continue with existing logic to launch Procfile
        # ...
      end

      private

      def self.run_precompile_hook_if_present
        return unless shakapacker_config_exists?

        config = load_shakapacker_config
        hook = config.dig('default', 'precompile_hook') ||
               config.dig(Rails.env, 'precompile_hook')

        return unless hook

        puts "Running precompile hook: #{hook}"
        unless system(hook)
          puts "Precompile hook failed!"
          exit(1)
        end
      end

      def self.shakapacker_config_exists?
        File.exist?(Rails.root.join('config/shakapacker.yml'))
      end

      def self.load_shakapacker_config
        require 'yaml'
        YAML.load_file(Rails.root.join('config/shakapacker.yml'))
      rescue => e
        warn "Could not load shakapacker.yml: #{e.message}"
        {}
      end
    end
  end
end

Example Flow

With precompile_hook configured:

# config/shakapacker.yml
default: &default
  precompile_hook: "yarn res:build && bundle exec rake react_on_rails:locale"

When user runs bin/dev:

  1. bin/dev starts
  2. Detects precompile_hook in shakapacker.yml
  3. Runs hook once: yarn res:build && bundle exec rake react_on_rails:locale
  4. Sets SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true
  5. Launches Procfile.dev processes (which inherit the ENV variable)
  6. Each webpack process sees the skip flag and doesn't re-run the hook

Procfile.dev (cleaned up):

# Before (with manual tasks and sleep hacks):
rescript: yarn res:dev
wp-client: sleep 15 && bin/shakapacker-dev-server
wp-server: sleep 15 && bundle exec rake react_on_rails:locale && bin/shakapacker --watch

# After (precompile hook handles it):
rescript: yarn res:dev
wp-client: bin/shakapacker-dev-server
wp-server: HMR=true SERVER_BUNDLE_ONLY=yes bin/shakapacker --watch

Clean! No sleep delays, no manual task duplication.

Benefits

  1. Single execution - Precompile hook runs exactly once when starting development
  2. No race conditions - Files generated before any webpack process starts
  3. Clean Procfiles - Remove sleep hacks and manual task duplication
  4. Consistent behavior - Same pattern for development, CI, and production
  5. Backward compatible - If no precompile_hook configured, behavior unchanged

Edge Cases

Multiple bin/dev modes

React on Rails supports different modes (hmr, static, prod). The hook should run for all:

def self.run_from_command_line(args)
  # Parse mode from args if needed
  mode = parse_mode(args)

  # Run hook regardless of mode
  run_precompile_hook_if_present
  ENV['SHAKAPACKER_SKIP_PRECOMPILE_HOOK'] = 'true'

  # Launch appropriate Procfile for mode
  start(mode, get_procfile_for_mode(mode))
end

Hook failures

If the precompile hook fails, bin/dev should exit immediately rather than launching processes:

unless system(hook)
  puts "❌ Precompile hook failed: #{hook}"
  exit(1)
end

No shakapacker.yml

If shakapacker.yml doesn't exist (non-webpack projects), gracefully skip:

def self.shakapacker_config_exists?
  File.exist?(Rails.root.join('config/shakapacker.yml'))
end

Implementation Checklist

  • Add run_precompile_hook_if_present to ReactOnRails::Dev::ServerManager
  • Update all bin/dev modes (default/hmr, static, prod) to run hook and set ENV
  • Add error handling for hook failures (exit early)
  • Add graceful handling when shakapacker.yml doesn't exist
  • Update documentation with new flow
  • Update example Procfile.dev in docs (remove manual tasks)
  • Add tests for hook execution and ENV variable setting
  • Update migration guide for existing projects

Documentation Updates

README section:

## Development Server with Precompile Hooks

React on Rails `bin/dev` automatically runs Shakapacker precompile hooks before
starting development processes:

1. Configure your precompile hook in `config/shakapacker.yml`:
   ```yaml
   default: &default
     precompile_hook: "yarn res:build && bundle exec rake react_on_rails:locale"
  1. Run bin/dev - it will:

    • Execute the precompile hook once
    • Launch all Procfile processes with SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true
    • Individual webpack processes skip the hook (already executed)
  2. Clean up your Procfile.dev - remove manual task execution:

    # ❌ Old way:
    wp-server: sleep 15 && bundle exec rake react_on_rails:locale && bin/shakapacker --watch
    
    # ✅ New way:
    wp-server: bin/shakapacker --watch

## Related Issues

- shakacode/shakapacker#849 - Add precompile_hook support with SHAKAPACKER_SKIP_PRECOMPILE_HOOK
- shakacode/react_on_rails#2090 - Make locale generation idempotent

## Version

React on Rails: 16.2.0.beta.12
Rails: 8.1.1
Shakapacker: 9.3.3

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions