Getting Started
    Core Concepts
    Building Features
    API Reference
    Configuration
    Deployment
    Upgrading
    Migrating
    Pro
    Misc
    Shakacode logoShakaCodeDeveloped by

    Copyright 2026 ShakaCode

    Extensible bin/dev Precompile Pattern

    This guide describes an alternative approach to handling precompile tasks that provides more flexibility than the default precompile_hook mechanism. This pattern is especially useful for projects with custom build requirements.

    Overview

    React on Rails offers two approaches for running tasks before webpack compilation:

    ApproachBest ForComplexity
    Default (precompile_hook)Simple projects, single precompile taskLow
    Extensible (bin/dev)Custom build steps, multiple tasks, version manager issuesMedium

    When to Use This Pattern

    Consider this approach if you:

    • Have multiple precompile tasks (ReScript, TypeScript compilation, custom locale generation)
    • Experience version manager issues (mise, asdf, rbenv) with rake tasks
    • Want cleaner Procfiles without embedded precompile logic
    • Need direct Ruby API access for faster execution
    • Want a single place to manage all precompile tasks

    Implementation

    1. Customize bin/dev

    The React on Rails generator creates a bin/dev script with an extensible precompile pattern. Uncomment and customize the run_precompile_tasks method:

    #!/usr/bin/env ruby
    # frozen_string_literal: true
    
    def run_precompile_tasks
      require_relative "../config/environment"
    
      puts "📦 Running precompile tasks..."
    
      # Example: Build ReScript files
      print "   ReScript build... "
      unless system("yarn res:build")
        puts "❌"
        exit(1)
      end
      puts "✅"
    
      # Locale generation via direct Ruby API (faster, no shell issues)
      # compile handles all edge cases gracefully: prints warnings if no locale
      # files found, skips if output files are up-to-date, safe to call always.
      # Exceptions (e.g., missing directories) bubble up and stop the server,
      # which surfaces configuration issues early.
      print "   Locale generation... "
      ReactOnRails::Locales.compile if ReactOnRails.configuration.i18n_dir.present?
      puts "✅"
    
      # Add more custom tasks as needed
      # print "   Custom task... "
      # YourCustomModule.run
      # puts "✅"
    
      puts ""
    end
    
    require "bundler/setup"
    require "react_on_rails/dev"
    
    DEFAULT_ROUTE = "hello_world"
    
    argv_with_defaults = ARGV.dup
    argv_with_defaults.push("--route=#{DEFAULT_ROUTE}") unless argv_with_defaults.any? { |arg| arg.start_with?("--route") }
    
    # Run precompile tasks before starting server (except for kill/help commands)
    unless ARGV.include?("kill") || ARGV.include?("-h") || ARGV.include?("--help") || ARGV.include?("help")
      run_precompile_tasks
    end
    
    ReactOnRails::Dev::ServerManager.run_from_command_line(argv_with_defaults)

    2. Configure shakapacker.yml

    Remove or comment out the precompile_hook in config/shakapacker.yml, since bin/dev now handles precompile tasks directly:

    Before (default precompile_hook approach):

    default: &default # ... other settings ...
      precompile_hook: 'bundle exec rake react_on_rails:locale'

    After (extensible bin/dev approach):

    default: &default
      # ... other settings ...
    
      # precompile_hook is not used here because:
      # - In development: bin/dev runs precompile tasks before starting processes
      # - In production: build_production_command includes all build steps
      # precompile_hook not configured - handled by bin/dev instead
    

    3. Clean Procfiles

    Remove precompile logic from your Procfiles:

    Before (embedded precompile logic):

    # Procfile.dev - Old approach with duplicated precompile
    rescript: yarn res:watch
    rails: bundle exec rails server -p 3000
    wp-client: sleep 15 && bundle exec rake react_on_rails:locale && bin/shakapacker-dev-server
    wp-server: SERVER_BUNDLE_ONLY=yes bin/shakapacker --watch

    After (clean and simple):

    # Procfile.dev - Clean approach with precompile in bin/dev
    rescript: yarn res:watch
    rails: bundle exec rails server -p 3000
    wp-client: bin/shakapacker-dev-server
    wp-server: SERVER_BUNDLE_ONLY=yes bin/shakapacker --watch

    4. Configure Build Commands

    Handle production builds in config/initializers/react_on_rails.rb:

    ReactOnRails.configure do |config|
      # Build commands should include all necessary steps
      config.build_test_command = "yarn res:build && RAILS_ENV=test bin/shakapacker"
      config.build_production_command = "yarn res:build && RAILS_ENV=production NODE_ENV=production bin/shakapacker"
    end

    Direct Ruby API Reference

    ReactOnRails::Locales.compile

    Generates locale files for i18n support.

    # Basic usage - skips if files are up-to-date
    ReactOnRails::Locales.compile
    
    # Force regeneration
    ReactOnRails::Locales.compile(force: true)

    This method:

    • Reads YAML locale files from config.i18n_yml_dir (or Rails i18n load path)
    • Generates JavaScript/JSON files in config.i18n_dir
    • Skips generation if output files are newer than source files (unless force: true)
    • Supports both JSON and JavaScript output formats based on config.i18n_output_format

    ReactOnRails::PacksGenerator

    Generates webpack pack files for auto-bundling.

    # Generate packs if stale (used by bin/dev automatically)
    ReactOnRails::PacksGenerator.instance.generate_packs_if_stale

    Benefits Comparison

    AspectDefault (precompile_hook)Extensible (bin/dev)
    Custom build stepsModify hook script (mixing concerns)Add to run_precompile_tasks method
    Procfile clarityMay need embedded shell commandsClean, single-purpose processes
    Locale generationVia rake task (slow, shell issues)Direct ReactOnRails::Locales.compile (fast)
    Version manager compatibilityRake task may use wrong RubyDirect Ruby call uses correct version
    DebuggingMultiple indirection layersClear sequential execution
    When precompile runsBefore each webpack compileOnce at dev server startup

    Execution Timing

    With this pattern, precompile tasks run:

    • Once when you start bin/dev
    • On manual restarts of bin/dev
    • Not on file changes during development
    • Not on webpack hot reload

    For file-watching behavior (e.g., ReScript watch mode), add a separate Procfile process instead. For production builds, ensure all tasks are included in build_production_command.

    Troubleshooting

    Version Manager Issues

    If you experience issues where rake tasks use the wrong Ruby version (common with mise, asdf, or rbenv in non-interactive shells):

    1. Use the direct Ruby API in bin/dev instead of rake tasks
    2. The Rails environment loaded in bin/dev will use the correct Ruby version

    Missing Environment

    If you see "uninitialized constant ReactOnRails" errors:

    # Ensure Rails environment is loaded before using React on Rails APIs
    require_relative "../config/environment"

    Precompile Tasks Running Multiple Times

    If using this pattern, ensure you:

    1. Remove the precompile_hook from shakapacker.yml
    2. Remove precompile commands from Procfile entries
    3. Only call run_precompile_tasks once in bin/dev

    FAQ

    When should I NOT use this pattern?

    Stick with the default precompile_hook approach if:

    • You only have a single precompile task (e.g., locale generation)
    • Your version manager works fine with rake tasks in all contexts
    • You prefer Shakapacker to handle precompile timing automatically

    The extensible pattern adds configuration overhead that isn't justified for simple setups.

    Compatibility

    This pattern requires React on Rails 16.2+ and works with any version of Shakapacker. The ReactOnRails::Locales.compile API has been available since React on Rails introduced i18n support and is the same method used internally by the react_on_rails:locale rake task.

    See Also