Skip to content

Enhancement: Extensible bin/dev precompile pattern as alternative to precompile_hook #2347

@justin808

Description

@justin808

Summary

The react-webpack-rails-tutorial migration to v16 revealed a more flexible and cleaner approach to handling precompile tasks than the default precompile_hook mechanism. This approach better supports projects with custom build requirements (e.g., ReScript, TypeScript compilation, custom locale generation).

Current Default Approach

React on Rails uses Shakapacker's precompile_hook in shakapacker.yml:

precompile_hook: 'bin/shakapacker-precompile-hook'

This hook:

  1. Runs before webpack compilation
  2. Calls ReactOnRails::PacksGenerator.instance.generate_packs_if_stale
  3. Is designed primarily for auto-bundled component packs generation

Limitations:

  • Projects with additional precompile needs (ReScript, custom locale handling) must either:
    • Modify the hook script (mixing concerns)
    • Add precompile tasks to Procfile commands (duplicated, error-prone)
  • The hook mechanism adds indirection and complexity
  • Embedded precompile tasks in Procfiles are harder to maintain

Proposed Alternative: Extensible bin/dev with Explicit Precompile

The react-webpack-rails-tutorial branch uses this cleaner pattern:

1. Custom precompile in bin/dev (NOT in precompile_hook)

# bin/dev
def run_precompile_tasks
  require_relative "../config/environment"
  
  puts "📦 Running precompile tasks..."
  
  # Project-specific: Build ReScript files
  print "   ReScript build... "
  system("yarn res:build") or exit(1)
  puts "✅"
  
  # Locale generation via direct Ruby API (faster, no shell issues)
  print "   Locale generation... "
  ReactOnRails::Locales.compile
  puts "✅"
end

# Only run precompile for server start commands
unless ARGV.include?("kill") || ARGV.include?("-h")
  run_precompile_tasks
end

ReactOnRails::Dev::ServerManager.run_from_command_line(ARGV)

2. Disable precompile_hook in shakapacker.yml

default: &default
  shakapacker_precompile: false
  # Note: 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

3. Clean Procfiles (no embedded precompile logic)

# Procfile.dev - Clean and simple
rescript: yarn res:watch
rails: bundle exec thrust bin/rails server -p 3000
wp-client: RAILS_ENV=development bin/shakapacker-dev-server
wp-server: SERVER_BUNDLE_ONLY=yes bin/shakapacker --watch

Compare to the old approach with embedded precompile:

# Old approach - duplicated precompile in Procfile
webpack: sh -c 'bundle exec rake react_on_rails:locale && rm -rf public/packs/* || true && bin/shakapacker -w'

4. Build commands handle production

# config/initializers/react_on_rails.rb
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"

Benefits of This Approach

Aspect Default (precompile_hook) Proposed (bin/dev)
Custom build steps Modify hook script (mixing concerns) Add to run_precompile_tasks method
Procfile clarity Embedded shell commands Clean, single-purpose processes
Locale generation Via rake task (slow, shell issues) Direct ReactOnRails::Locales.compile (fast)
Version manager compatibility Rake task may use wrong Ruby Direct Ruby call uses correct version
Debugging Multiple indirection layers Clear sequential execution
When precompile runs Before each webpack compile Once at dev server startup

Specific Improvements

1. Direct Ruby API for Locale Generation

Instead of:

bundle exec rake react_on_rails:locale

Use:

ReactOnRails::Locales.compile

This avoids:

  • Shell spawning overhead
  • Version manager PATH issues (mise, asdf, rbenv)
  • Duplicate Rails environment loading

2. Extensible Precompile Pattern

Provide a hook point in bin/dev or a new configuration option:

# Option A: Configuration-based
ReactOnRails.configure do |config|
  config.precompile_tasks = [
    -> { system("yarn res:build") },
    -> { ReactOnRails::Locales.compile },
  ]
end

# Option B: Documented bin/dev pattern
# bin/dev template includes clear extension point
def run_precompile_tasks
  # Add your custom precompile tasks here
  # Example: system("yarn res:build")
  
  ReactOnRails::Locales.compile if ReactOnRails.configuration.i18n_dir.present?
end

3. Document the "No precompile_hook" Pattern

For projects that don't use auto-bundling or have custom build requirements, document:

# shakapacker.yml - Alternative: explicit precompile in bin/dev
default: &default
  shakapacker_precompile: false
  # precompile_hook: not configured - handled by bin/dev

Proposal

  1. Add extensible precompile support to bin/dev template: Include a run_precompile_tasks method with clear extension points

  2. Expose ReactOnRails::Locales.compile as public API: Document its use for custom bin/dev scripts

  3. Document the "no precompile_hook" pattern: For projects with custom build requirements

  4. Add configuration option for precompile tasks: Allow registering custom tasks via configuration

  5. Update Procfile templates: Remove embedded precompile logic when bin/dev handles it

Reference Implementation

  • Branch: justin808/shakapacker-early-hints in react-webpack-rails-tutorial
  • Key files: bin/dev, config/shakapacker.yml, Procfile.dev
  • Related issues: Conductor multi-version-manager support (Doc Changes for links on gitbook #692)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions