Asset-pipeline

Learn about asset-pipeline in Ruby on Rails development.

Beginner 1 min read

Asset Pipeline

Master Rails' traditional Asset Pipeline (Sprockets) for managing CSS, JavaScript, and images in your Rails application.

Asset Pipeline Overview

# Gemfile - Asset Pipeline dependencies
gem 'sprockets-rails'  # Asset pipeline
gem 'sassc-rails'      # Sass support
gem 'uglifier'         # JS minification
gem 'coffee-rails'     # CoffeeScript support (optional)

# Asset pipeline configuration
# config/application.rb
module MyApp
  class Application < Rails::Application
    # Enable the asset pipeline
    config.assets.enabled = true
    
    # Asset paths
    config.assets.paths << Rails.root.join('app', 'assets', 'fonts')
    config.assets.paths << Rails.root.join('vendor', 'assets', 'stylesheets')
    
    # Precompile additional assets
    config.assets.precompile += %w[admin.css admin.js mobile.css]
    
    # Asset version (for cache busting)
    config.assets.version = '1.0'
  end
end

Asset Pipeline Features

  • Concatenation: Combines multiple files to reduce HTTP requests
  • Minification: Removes whitespace and optimizes code
  • Preprocessing: Supports Sass, CoffeeScript, ERB, and more
  • Fingerprinting: Adds unique fingerprints for cache busting
  • Gzip compression: Compresses assets for faster delivery

Asset Directory Structure

# Rails asset directory structure
app/
├── assets/
│   ├── config/
│   │   └── manifest.js          # Sprockets manifest
│   ├── stylesheets/
│   │   ├── application.css      # Main CSS manifest
│   │   ├── components/          # Component-specific styles
│   │   ├── pages/              # Page-specific styles
│   │   └── shared/             # Shared styles
│   ├── javascripts/
│   │   ├── application.js       # Main JS manifest
│   │   ├── components/         # JS components
│   │   └── pages/             # Page-specific JS
│   └── images/                 # Images and icons

vendor/
└── assets/
    ├── stylesheets/            # Third-party CSS
    └── javascripts/            # Third-party JS

lib/
└── assets/
    ├── stylesheets/            # Library CSS
    └── javascripts/            # Library JS
// app/assets/config/manifest.js
//= link_tree ../images
//= link_directory ../javascripts .js
//= link_directory ../stylesheets .css

// Link specific files
//= link application.css
//= link application.js
//= link admin.css
//= link admin.js

CSS & Sass Integration

/* app/assets/stylesheets/application.css */
/*
 * This is a manifest file that'll be compiled into application.css
 * 
 *= require_tree .
 *= require_self
 */

/* Your CSS rules go here */
body {
  font-family: Arial, sans-serif;
  margin: 0;
  padding: 0;
}
// app/assets/stylesheets/application.scss
// Sass version with imports

// Import external libraries
@import "bootstrap";

// Import your custom styles
@import "base/variables";
@import "base/mixins";
@import "base/typography";

@import "components/buttons";
@import "components/forms";
@import "components/cards";

@import "pages/home";
@import "pages/dashboard";

// Variables file: app/assets/stylesheets/base/_variables.scss
$primary-color: #007bff;
$secondary-color: #6c757d;
$font-size-base: 16px;
$line-height-base: 1.5;

// Responsive breakpoints
$mobile: 768px;
$tablet: 1024px;
$desktop: 1200px;
// Component-specific styles
// app/assets/stylesheets/components/_buttons.scss
.btn {
  display: inline-block;
  padding: 0.375rem 0.75rem;
  margin-bottom: 0;
  font-size: $font-size-base;
  font-weight: 400;
  line-height: $line-height-base;
  text-align: center;
  white-space: nowrap;
  vertical-align: middle;
  cursor: pointer;
  border: 1px solid transparent;
  border-radius: 0.25rem;
  transition: all 0.15s ease-in-out;

  &.btn-primary {
    color: white;
    background-color: $primary-color;
    border-color: $primary-color;

    &:hover {
      background-color: darken($primary-color, 7.5%);
      border-color: darken($primary-color, 10%);
    }
  }
}

JavaScript Management

// app/assets/javascripts/application.js
// This is a manifest file that'll be compiled into application.js
//
//= require rails-ujs
//= require turbo
//= require_tree .

// Your JavaScript code goes here
document.addEventListener('DOMContentLoaded', function() {
  console.log('Application loaded');
  
  // Initialize components
  initializeNavigation();
  initializeForms();
});
// Component-based organization
// app/assets/javascripts/components/navigation.js
function initializeNavigation() {
  const navToggle = document.querySelector('.nav-toggle');
  const navMenu = document.querySelector('.nav-menu');
  
  if (navToggle && navMenu) {
    navToggle.addEventListener('click', function() {
      navMenu.classList.toggle('active');
    });
  }
}

// app/assets/javascripts/components/forms.js
function initializeForms() {
  // Form validation
  const forms = document.querySelectorAll('form[data-validate]');
  forms.forEach(form => {
    form.addEventListener('submit', validateForm);
  });
  
  // AJAX forms
  const ajaxForms = document.querySelectorAll('form[data-remote]');
  ajaxForms.forEach(form => {
    form.addEventListener('ajax:success', handleAjaxSuccess);
    form.addEventListener('ajax:error', handleAjaxError);
  });
}

function validateForm(event) {
  // Custom validation logic
  const form = event.target;
  const requiredFields = form.querySelectorAll('[required]');
  
  let isValid = true;
  requiredFields.forEach(field => {
    if (!field.value.trim()) {
      showFieldError(field, 'This field is required');
      isValid = false;
    }
  });
  
  if (!isValid) {
    event.preventDefault();
  }
}
# CoffeeScript support (optional)
# app/assets/javascripts/dashboard.coffee

class Dashboard
  constructor: (@element) ->
    @initializeCharts()
    @bindEvents()
  
  initializeCharts: ->
    chartData = @element.dataset.chartData
    # Initialize charts with data
  
  bindEvents: ->
    @element.addEventListener 'click', @handleClick.bind(@)
  
  handleClick: (event) ->
    # Handle dashboard interactions

$ ->
  dashboards = document.querySelectorAll('.dashboard')
  dashboards.forEach (element) ->
    new Dashboard(element)

Asset Helpers

<!-- In your ERB templates -->

<!-- CSS stylesheets -->
<%= stylesheet_link_tag 'application', 'data-turbo-track': 'reload' %>
<%= stylesheet_link_tag 'admin' if admin_user? %>

<!-- JavaScript -->
<%= javascript_importmap_tags %>
<%= javascript_include_tag 'application', 'data-turbo-track': 'reload' %>

<!-- Images -->
<%= image_tag 'logo.png', alt: 'Company Logo', class: 'logo' %>
<%= image_tag 'hero-bg.jpg', class: 'hero-background' %>

<!-- Asset URLs in CSS -->
<%= asset_url('background.jpg') %>
<%= asset_path('icons/star.svg') %>

<!-- Conditional assets -->
<% if Rails.env.development? %>
  <%= javascript_include_tag 'development-tools' %>
<% end %>
// Using asset helpers in Sass
.hero {
  background-image: asset-url('hero-background.jpg');
  background-size: cover;
  background-position: center;
}

.icon-star {
  background-image: asset-url('icons/star.svg');
  width: 24px;
  height: 24px;
}
# In controllers or helpers
class ApplicationController < ActionController::Base
  private
  
  def additional_stylesheets
    stylesheets = ['application']
    stylesheets << 'admin' if current_user&.admin?
    stylesheets << 'mobile' if mobile_device?
    stylesheets
  end
end

# Custom asset helper
module AssetsHelper
  def responsive_image_tag(image, options = {})
    sizes = options.delete(:sizes) || {}
    
    tag.picture do
      sizes.each do |breakpoint, size|
        concat tag.source(
          srcset: asset_path("#{size}/#{image}"),
          media: "(min-width: #{breakpoint}px)"
        )
      end
      concat image_tag(image, options)
    end
  end
end

Asset Precompilation

# Precompile assets for production
rails assets:precompile

# Precompile with specific environment
RAILS_ENV=production rails assets:precompile

# Clean precompiled assets
rails assets:clean

# Clean all assets (including older versions)
rails assets:clobber

# Check asset compilation status
rails assets:environment
# config/environments/production.rb
Rails.application.configure do
  # Compress CSS using a preprocessor
  config.assets.css_compressor = :sass
  
  # Compress JavaScripts using a JS compressor
  config.assets.js_compressor = :terser
  
  # Do not fallback to assets pipeline if a precompiled asset is missed
  config.assets.compile = false
  
  # Generate digests for assets URLs
  config.assets.digest = true
  
  # Enable serving of images, stylesheets, and JavaScripts from an asset server
  # config.asset_host = 'http://assets.example.com'
  
  # Specifies the header that your server uses for sending files
  config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
  
  # Force all access to the app over SSL
  config.force_ssl = true
end
# Custom precompile configuration
# config/application.rb
class Application < Rails::Application
  # Precompile additional assets
  config.assets.precompile += %w[
    admin.css admin.js
    mobile.css mobile.js
    dashboard/*.css
    components/*.js
  ]
  
  # Add custom paths
  config.assets.paths << Rails.root.join('app', 'assets', 'fonts')
  config.assets.paths << Rails.root.join('app', 'assets', 'videos')
  
  # Asset pipeline configuration for different file types
  config.assets.precompile += %w[*.png *.jpg *.jpeg *.gif *.svg]
  config.assets.precompile += %w[*.woff *.woff2 *.ttf *.eot]
end

CDN & Performance

# CDN configuration
# config/environments/production.rb
Rails.application.configure do
  # Asset host for CDN
  config.asset_host = 'https://cdn.myapp.com'
  
  # Or dynamic asset host
  config.asset_host = Proc.new { |source|
    if source.starts_with?('/assets')
      "https://assets#{source.hash % 4}.myapp.com"
    end
  }
  
  # CloudFront distribution
  config.asset_host = 'https://d1234567890.cloudfront.net'
end
# Asset performance optimizations
# config/initializers/assets.rb

# Gzip compression
Rails.application.config.assets.configure do |env|
  env.gzip = true
end

# Cache assets
Rails.application.config.assets.configure do |env|
  env.cache = Sprockets::Cache::FileStore.new(
    File.join(Rails.root, 'tmp', 'cache', 'assets'),
    262144000, # 250MB
    env.logger
  )
end

# Custom asset processors
Rails.application.config.assets.configure do |env|
  # Image optimization processor
  env.register_processor('image/png', ImageOptimProcessor)
  env.register_processor('image/jpeg', ImageOptimProcessor)
end
# Custom asset optimization
class ImageOptimProcessor
  def self.call(input)
    data = input[:data]
    
    # Optimize images using ImageOptim or similar
    optimized = ImageOptim.new(
      pngout: false,
      svgo: false,
      pngcrush: { chunks: :alla, fix: false, brute: false }
    ).optimize_image_data(data)
    
    { data: optimized || data }
  end
end
Pro Tip: Use a CDN for static assets to improve loading times globally. Consider using multiple subdomains to overcome browser connection limits.

Best Practices

Organize assets logically: Use components/, pages/, and shared/ directories
Use Sass variables: Maintain consistency across stylesheets
Optimize images: Use appropriate formats and compression
Leverage CDN: Use asset hosts for better global performance
Precompile in production: Never compile assets on-demand in production
Monitor asset sizes: Keep bundles small for better loading performance