Skip
Arish's avatar

20. Asset Pipeline and CSS


The Asset Pipeline

The asset pipeline provides a framework for managing JavaScript, CSS, and images in your Rails application. Rails 7+ uses modern tools like Propshaft, Import Maps, and optionally Tailwind CSS.

Modern Rails Asset Management

Rails 7+ offers several options for managing frontend assets:

Import Maps (Default)

Import maps allow you to use JavaScript modules directly in the browser without bundling:

ruby
1# config/importmap.rb
2pin "application"
3pin "@hotwired/turbo-rails", to: "turbo.min.js"
4pin "@hotwired/stimulus", to: "stimulus.min.js"
5pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
6pin_all_from "app/javascript/controllers", under: "controllers"
javascript
1// app/javascript/application.js
2import "@hotwired/turbo-rails"
3import "controllers"

Adding JavaScript Libraries

bash
1# Add a package via import maps
2bin/importmap pin lodash

CSS in Rails 7+

CSS Bundling with Tailwind

Generate a new app with Tailwind:

bash
1rails new myapp --css=tailwind

Or add to existing app:

bash
1bundle add tailwindcss-rails
2rails tailwindcss:install
html
1<!-- app/views/layouts/application.html.erb -->
2<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
3<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>

Tailwind Configuration

javascript
1// config/tailwind.config.js
2module.exports = {
3  content: [
4    './app/views/**/*.html.erb',
5    './app/helpers/**/*.rb',
6    './app/assets/stylesheets/**/*.css',
7    './app/javascript/**/*.js'
8  ],
9  theme: {
10    extend: {
11      colors: {
12        primary: '#3B82F6',
13        secondary: '#10B981'
14      }
15    }
16  },
17  plugins: []
18}

Custom CSS

css
1/* app/assets/stylesheets/application.css */
2@import "tailwindcss/base";
3@import "tailwindcss/components";
4@import "tailwindcss/utilities";
5
6/* Custom styles */
7@layer components {
8  .btn {
9    @apply px-4 py-2 rounded font-semibold;
10  }
11  
12  .btn-primary {
13    @apply bg-blue-500 text-white hover:bg-blue-600;
14  }
15  
16  .card {
17    @apply bg-white rounded-lg shadow-md p-6;
18  }
19}

Image Assets

Image Tag Helper

erb
1<%= image_tag "logo.png" %>
2<%= image_tag "hero.jpg", class: "w-full h-64 object-cover" %>
3<%= image_tag "avatar.png", alt: "User avatar", size: "50x50" %>

Images in CSS

css
1.hero {
2  background-image: url('hero.jpg');
3}

Active Storage Images

For user-uploaded images:

erb
1<%= image_tag @user.avatar if @user.avatar.attached? %>
2<%= image_tag @user.avatar.variant(resize_to_limit: [100, 100]) %>

Stimulus Controllers

Stimulus is Rails' modest JavaScript framework:

bash
1rails generate stimulus hello
javascript
1// app/javascript/controllers/hello_controller.js
2import { Controller } from "@hotwired/stimulus"
3
4export default class extends Controller {
5  static targets = ["name", "output"]
6  
7  greet() {
8    this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!`
9  }
10}
erb
1<!-- Using the controller -->
2<div data-controller="hello">
3  <input data-hello-target="name" type="text">
4  <button data-action="click->hello#greet">Greet</button>
5  <span data-hello-target="output"></span>
6</div>

Common Stimulus Patterns

javascript
1// Toggle visibility
2import { Controller } from "@hotwired/stimulus"
3
4export default class extends Controller {
5  static targets = ["content"]
6  
7  toggle() {
8    this.contentTarget.classList.toggle("hidden")
9  }
10}
11
12// Form validation
13import { Controller } from "@hotwired/stimulus"
14
15export default class extends Controller {
16  static targets = ["email", "submit"]
17  
18  validate() {
19    const isValid = this.emailTarget.value.includes("@")
20    this.submitTarget.disabled = !isValid
21  }
22}

Turbo for Fast Navigation

Turbo makes your app feel like a SPA without writing JavaScript:

Turbo Drive

Automatically converts links and forms to AJAX:

erb
1<!-- Regular link - Turbo Drive intercepts this -->
2<%= link_to "Articles", articles_path %>
3
4<!-- Disable Turbo for specific link -->
5<%= link_to "External", "https://example.com", data: { turbo: false } %>

Turbo Frames

Update only part of the page:

erb
1<!-- articles/index.html.erb -->
2<%= turbo_frame_tag "articles" do %>
3  <% @articles.each do |article| %>
4    <%= render article %>
5  <% end %>
6  
7  <%= link_to "Load More", articles_path(page: @page + 1) %>
8<% end %>

Turbo Streams

Real-time updates:

ruby
1# articles_controller.rb
2def create
3  @article = Article.create(article_params)
4  
5  respond_to do |format|
6    format.turbo_stream
7    format.html { redirect_to articles_path }
8  end
9end
erb
1<!-- app/views/articles/create.turbo_stream.erb -->
2<%= turbo_stream.prepend "articles", @article %>
3<%= turbo_stream.update "new_article_form", partial: "articles/form" %>

Production Asset Compilation

bash
1# Precompile assets for production
2rails assets:precompile
3
4# Clean old assets
5rails assets:clean
6
7# Full clean
8rails assets:clobber

Asset Digest and Fingerprinting

Rails automatically adds fingerprints to assets for cache busting:

html
1<!-- Development -->
2<link href="/stylesheets/application.css">
3
4<!-- Production -->
5<link href="/assets/application-a1b2c3d4.css">

CDN Configuration

ruby
1# config/environments/production.rb
2config.asset_host = "https://assets.example.com"

Common Patterns

Conditional CSS Classes

erb
1<div class="card <%= 'featured' if @article.featured? %>">
2  <%= @article.title %>
3</div>
4
5<%= content_tag :div, @article.title, 
6    class: ['card', ('featured' if @article.featured?)] %>

Dynamic Styles

erb
1<div style="background-color: <%= @user.theme_color %>;">
2  Profile
3</div>

Modern Rails asset management is simpler and more powerful than ever!