Railpack 🎒
Multi-bundler asset pipeline for Rails - Choose your bundler, unified Rails integration.
Installation
- Add to Gemfile:
gem "railpack" - Run:
bin/rails railpack:install# Generator + initial dependencies - Edit config/railpack.yml to configure/switch bundlers (Railpack creates
config/railpack.ymlwith sensible defaults for your Rails app.) - Commands:
-
bin/rails railpack:install# Dependencies (auto on deploy) -
bin/rails railpack:build# Assets -
bin/rails railpack:watch# Dev live reload -
bin/rails railpack:reload# Reload config without restart
-
Features
- 🚀 Multiple Bundlers: Bun, esbuild, Rollup, Webpack support
- 🔧 Unified API: Same interface regardless of bundler
- 🎯 Rails Integration: Seamless asset pipeline integration
- âš¡ Hot Module Replacement: Development server with live reload
- 🎣 Event Hooks: Build lifecycle callbacks
- 📦 Production Ready: Optimized builds for all bundlers
Configuration
Create config/railpack.yml:
# Choose your bundler
bundler: bun # or 'rollup', 'webpack', 'esbuild'
# Global defaults
default:
target: browser
format: esm
minify: false
sourcemap: false
entrypoint: "./app/javascript/application.js"
outdir: "app/assets/builds"
analyze_bundle: false # Enable for gzip size reporting
# Bundler-specific config
bun:
target: browser
format: esm
# Environment overrides
development:
sourcemap: true
analyze_bundle: true # Show gzip sizes in dev
production:
minify: true
analyze_bundle: true # Production bundle analysisAdvanced Configuration
Per-Bundler Command Overrides
Override default commands for specific bundlers:
bundlers:
esbuild:
commands:
build: "esbuild-custom --special-flag"
watch: "esbuild-custom --watch --dev-mode"
version: "esbuild-custom --version-check"
bun:
commands:
build: "bunx custom-build"
rollup:
commands:
build: "rollup --config custom-config.js"
webpack:
commands:
build: "webpack --config custom-webpack.config.js"Watch Flags Configuration
Configure watch-specific flags (different from build flags):
esbuild:
target: browser
format: esm
watch_flags: ["--watch", "--serve=3000"] # Custom watch flags
rollup:
format: esm
sourcemap: true
watch_flags: ["--watch"]
webpack:
mode: production
target: web
watch_flags: ["--watch"]Dynamic Bundler Switching
Switch bundlers per environment or dynamically:
# Switch bundlers per environment
development:
bundler: bun # Fast for development
production:
bundler: esbuild # Speed for production
# Or use different bundlers for different tasks
bundlers:
esbuild:
commands:
build: "esbuild --production"
webpack:
commands:
build: "webpack --config webpack.prod.js"Custom Entry Points and Outputs
Configure multiple entry points and custom outputs:
default:
entrypoints: ["./app/javascript/application.js", "./app/javascript/admin.js"]
outdir: "app/assets/builds"
# Or single entrypoint
default:
entrypoint: "./app/javascript/application.js"
outdir: "app/assets/builds"Usage
Basic Commands
# Build for production
rails railpack:build
# Watch and rebuild during development
rails railpack:watch
# Reload configuration without server restart
rails railpack:reload
# Install dependencies
rails railpack:install
# Check current bundler
rails railpack:bundlerProgrammatic API
# Build assets
Railpack.build!
# Watch for changes
Railpack.watch
# Install packages
Railpack.install!
# Add dependencies
Railpack.add('lodash', 'axios')Rails Integration
Railpack automatically integrates with Rails asset pipeline:
# config/initializers/railpack.rb
require 'railpack'
# Override config at runtime
Railpack.config.sourcemap = true
# Setup logging
Railpack.logger = Rails.logger
# Build event hooks
Railpack.on_build_complete do |result|
Rails.logger.info "Build completed: #{result[:success]}"
endAdvanced Usage
Bundle Analysis with Gzip
Enable analyze_bundle: true to see both uncompressed and gzipped bundle sizes:
# config/railpack.yml
default:
analyze_bundle: trueOutput example:
✅ Build completed successfully in 45.23ms (1.23 MB (0.45 MB gzipped))
Build Hooks with Payload Details
Hook payloads provide detailed information about build results:
# config/initializers/railpack.rb
Railpack.on_build_complete do |payload|
if payload[:success]
# Success payload: { success: true, config: {...}, duration: 45.23, bundle_size: "1.23 MB" }
Rails.logger.info "Build succeeded in #{payload[:duration]}ms - #{payload[:bundle_size]}"
else
# Error payload: { success: false, error: #<Error>, config: {...}, duration: 12.34 }
Rails.logger.error "Build failed after #{payload[:duration]}ms: #{payload[:error].message}"
end
end
Railpack.on_build_start do |config|
# Config hash contains all current settings
Rails.logger.info "Starting #{config['bundler']} build for #{Rails.env}"
endManifest Generation
Railpack automatically detects your asset pipeline and generates appropriate manifests:
# For Propshaft (Rails 7+ default)
# Generates: app/assets/builds/.manifest.json
Railpack::Manifest::Propshaft.generate(config)
# For Sprockets (Rails < 7)
# Generates: app/assets/builds/.sprockets-manifest-*.json
Railpack::Manifest::Sprockets.generate(config)The manifest generation is handled automatically after each build, but you can trigger it manually if needed.
Supported Bundlers
Bun (Default)
- Fast builds - Lightning-quick bundling
- All-in-one - Runtime, bundler, package manager
- Great DX - Excellent development experience
Rollup
- Tree shaking - Optimal bundle sizes
- Plugin ecosystem - Extensive customization
- ESM focus - Modern module system
Webpack
- Enterprise - Battle-tested in production
- Feature-rich - Extensive plugin ecosystem
- Legacy support - Handles all module types
esbuild
- Speed demon - 10-100x faster than alternatives
- Simple API - Minimal configuration
- Modern features - ESM, minification, sourcemaps
Switching Bundlers
Change the bundler setting in config/railpack.yml:
bundler: esbuild # Switch to esbuild for speedOr use esbuild:
bundler: esbuild
esbuild:
platform: browser
target: es2015
minify: trueRailpack handles the rest - same API, different bundler under the hood.
Development
Adding a New Bundler
- Create bundler class:
# lib/railpack/bundlers/my_bundler.rb
class Railpack::MyBundler < Railpack::Bundler
def commands
{ build: "my-build", watch: "my-watch" }
end
def build!(args = [])
execute!([commands[:build], *args])
end
end- Register in Manager:
BUNDLERS = {
'bun' => BunBundler,
'my' => MyBundler # Add here
}Contributing
- Fork the repo
- Add your bundler implementation
- Update documentation
- Submit a PR
Changelog
See CHANGELOG.md for version history.
License
MIT License - see LICENSE.txt
Credits
Built by 21tycoons LLC for the Rails community.