【Rails】アセットパイプライン(Sprockets)の基本情報と実装方法

はじめに

Ruby on Railsにはアセットパイプラインという機能があります。アセットパイプラインは画像、CSS、JavaScriptといったアセットファイルを連結/圧縮することでRailsアプリを高速化します。また、より高級な言語で書かれたCSSやJavaScriptをコンパイルする機能も備えています。

Rails 6からWebpackerが採用され、Sprocketsによるアセットパイプラインはいくらか局所的になりました。デフォルトでJavaScriptはWebpackerによって管理され、設定次第ではCSSや画像といったその他のアセットファイルについてもWebpackerで管理することが可能です。

本記事では、JavaScriptを含むすべてのアセットファイルをSprocketsによるアセットパイプラインで扱う方針でまとめています。

アセットパイプラインの基本

アセットパイプラインは、アセットファイルを連結/圧縮することでRailsアプリを高速化します。また、Sassで書かれたCSSやCoffeeScriptで書かれたJavaScript、Rubyコードが埋め込まれたCSS/JavaScriptをコンパイルする機能も備えたフレームワークです。アセットパイプラインはSprocketsをRails用にカスタマイズしたsprockets-railsというGemで実装されています。

アセットファイルの連結

アセットパイプラインによるアセッファイルトの連結の仕組みは、起点となるマニフェストファイルに連結対象のファイルを指定したディレクティブを記述することにより実現します。

JavaScriptの場合、マニフェストファイルはapp/assets/javascripts/ディレクトリ配下のapplication.jsです。マニフェストファイルにディレクティブというSprockets独自のコメントとともに連結対象のJavaScriptファイルを指定します。

application.js

//= require rails-ujs
//= require turbolinks
//= require_tree .

CSSの場合、マニフェストファイルはapp/assets/stylesheets/ディレクトリ配下のapplication.scssです。JavaScript同様、ディレクティブとともに連結対象のCSSファイルを指定します。

application.scss

/*
 *= require_tree .
 *= require_self
 */

アセットファイルの連結には以下のディレクティブを使うことができます。いろいろありますが、実際に使うのはrequirerequire_selfrequire_treeくらいなので、全部を覚えておく必要はありません。

ディレクティブ 意味
require 指定したファイルを連結する。
require_self マニフェストファイル自体の内容を連結する。
require_directory 指定したフォルダー内のファイルを連結する。
require_tree 指定したフォルダー内のファイルを再起的に連結する。
link 指定したファイルをコンパイルする(連結はしない)。
link_directory 指定したフォルダー内のファイルをコンパイルする(連結はしない)。
link_tree 指定したフォルダー内のファイルを再起的にコンパイルする(連結はしない)。
depend_on 指定したファイルが変更された場合に再コンパイルする。
stub 指定したファイルを無視する。

アセットファイルの圧縮

アセットファイルの連結が行われると同時に圧縮も行われます。ここで言う圧縮とは、ファイルの最小化や難読化のことを言い、ファイルをZIP形式などのアーカイブファイルにすることではありません。

CSSファイルの圧縮

CSSファイルは、ホワイトスペースやコメントを削除することで圧縮されます。CSSファイルの圧縮方式にはSassYUIの2種類があります(おそらくどちらも大差ないと思われます)。使用するにはそれぞれ以下のGemをインストールする必要があります。

Gemfile

# 以下のいずれかをインストール
gem 'sass-rails'
gem 'yui-compressor'

Sassを使って圧縮する場合は上記のGemをインストールすることで圧縮設定が有効になります。YUIを使って圧縮する場合はconfig/environments/production.rbに以下を追記します。

production.rb

config.assets.css_compressor = :yui

JavaScriptファイルの圧縮

JavaScriptファイルは、ホワイトスペースやコメントを削除することに加え、ローカル変数名を短縮し、可能であればif-elseステートメントを三項演算子に変換します。圧縮後は著しく可読性の低下したコードになるため、この圧縮方式のことを難読化などとも言います。

JavaScriptファイルの圧縮方式にはUglifierYUIClosureの3種類があります。使用するにはそれぞれ以下のGemをインストールする必要があります。

Gemfile

# 以下のいずれかをインストール
gem 'uglifier'
gem 'yui-compressor'
gem 'closure-compiler'

それぞれの圧縮方式を有効にするには、config/environments/production.rbに以下を追記します。

production.rb

# 以下のいずれかを設定
config.assets.js_compressor = :uglifier
config.assets.js_compressor = :yui
config.assets.js_compressor = :closure

高級言語のコンパイル

アセットパイプラインは、SassやCoffeeScriptといった高級な言語をコンパイルする機能も備えています。これらを使うには以下のGemが必要です(標準でインストールされています)。

Gemfile

gem 'sass-rails'
gem 'coffee-rails'

これらのGemがインストールされていると、ジェネレーターコマンドでコントローラーを作成したときに同時に作成されるCSS/JavaScriptファイルがそれぞれSass/CoffeeScript形式になります(Scaffold作成時も同様)。

$ rails generate controller Articles
# => app/assets/stylesheets/articles.scss
#    app/assets/javascripts/articles.coffee

ジェネレーターコマンド実行時にこれらのファイルを作成したくない場合は、config/application.rbに以下を追記します。

application.rb

module AppName
  class Application < Rails::Application
    # 以下を追記
    config.generators do |g|
      g.assets false
    end
  end
end

また、CSS/JavaScriptファイルにRubyコードを含めることもできます。Rubyコードを埋め込みたいCSS/JavaScriptファイルの名前にERBの拡張子.erbを付加します。

application.js.erb

const app = <%= Rails.application.class.name %>;

Sass、CoffeeScript、そしてERBはアセットパイプラインによってコンパイルされることで、最終的にひとつのCSS/JavaScriptファイルが生成されます。

コンパイルは拡張子の右から順に行われていきます。例えば、articles.scss.erbというファイルの場合、一番右にある.erbという拡張子からERBがコンパイルされ、次いで.scssという拡張子からSassがコンパイルされます。そして最終的にCSSファイルが出来上がるという流れです。articles.coffee.erbというファイルも同様に右から順にERB→CoffeeScript→JavaScriptと処理されていきます。

キャッシュの仕組み

アセットパイプラインにおけるキャッシュは、最終的に生成されたそれぞれのCSS/JavaScriptファイルの名前にフィンガープリント(ダイジェスト)を付加することで、アセットファイルがWebブラウザでキャッシュされるようにしています。

フィンガープリント(ダイジェスト)はSHA-256によってハッシュ化された文字列がファイル名に付加されます。ファイルの内容を変更するとハッシュ文字列が再作成され、新たなフィンガープリント(ダイジェスト)としてファイル名に付加されます。このハッシュ文字列は完全に一意と見做せるため、サーバー上のファイルとキャッシュのハッシュ文字列が異なるときだけファイルをリクエストすれば無駄な通信を減らすことができるという仕組みです。

以下はフィンガープリント(ダイジェスト)が付加されたJavaScriptファイルの例です。

application-1055d2f9a1a248bf051584d3572c2f2d79d103e956e0fcee48f1cab5cd40472b.js

アセットパイプラインの使用

アセットファイルの参照

アセットパイプラインによってコンパイルされたアセットファイルは、「キャッシュの仕組み」セクションで説明したようにファイル名にフィンガープリント(ダイジェスト)が付加されるため、通常のタグでは参照することができません。アセットファイルの参照はRailsのヘルパーメソッドを使います。

CSS/JavaScriptファイルの参照

CSS/JavaScriptファイルを参照するには以下のように記述します。

application.html.erb

<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>

application.html.slim

= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
= javascript_include_tag 'application', 'data-turbolinks-track': 'reload'

Turbolinksについては以下の記事を参照してください。

画像ファイルの参照(ビュー)

画像ファイルを参照するには以下のように記述します。

index.html.erb

<%= image_tag 'cover.png' %>

index.html.slim

= image_tag 'cover.png'

Sprocketsのヘルパーメソッドasset_pathを使うこともできます。

index.html.erb

<%= link_to asset_path('cover.png') do %>
  <%= image_tag 'cover.png' %>
<% end %>

index.html.slim

= link_to asset_path('cover.png') do
  = image_tag 'cover.png'

画像ファイルの参照(CSS)

CSSで背景画像などを設定したい場合、sass-railsのヘルパーメソッドimage-urlを使用します。以下の例の場合、image-url('cover.png')url('/assets/cover.png')に変換されます(フィンガープリント(ダイジェスト)は省略)。

background-image: image-url('cover.png');

アイコンファイルの参照

アイコンファイルを参照してFaviconを設定したい場合、favicon_link_tagを使用します。

application.html.erb

<%= favicon_link_tag 'favicon.ico' %>

application.html.slim

= favicon_link_tag 'favicon.ico'

アセットパスの追加

アセットパイプラインのアセットファイルは以下のディレクトリ配下に配置することができます。

ディレクトリ 意味
app/assets/ アプリケーション固有の画像、CSS、JavaScriptなどを配置。
lib/assets/ アプリケーション固有でない(複数のアプリケーションで共有される)ライブラリのコードを配置。
vendor/assets/ JavaScriptプラグインやCSSフレームワークなど、サードパーティー製のコードを配置。

これらのディレクトリ配下に配置したファイルは、デフォルトでアセットパイプラインのアセットファイルとして扱われると公式ガイドに書いてありましたが、実際に試したところvendor/assets/ディレクトリ配下に配置したファイルはデフォルトでは読み込まれませんでした。

アセットパイプラインのアセットパスを追加するには、config/initializers/ディレクトリのassets.rbに以下を追記します。

assets.rb

Rails.application.config.assets.paths << Rails.root.join("vendor", "assets", "stylesheets")
Rails.application.config.assets.paths << Rails.root.join("vendor", "assets", "javascripts")

Railsコンソールからアセットパスを確認することができます。

$ rails console

> pp Rails.application.config.assets.paths
["/Users/user/Products/app_name/app/assets/config",
 "/Users/user/Products/app_name/app/assets/images",
 "/Users/user/Products/app_name/app/assets/stylesheets",
 "/Users/user/Products/app_name/lib/assets/stylesheets",
 "/Users/user/Products/app_name/vendor/assets/stylesheets",
 #<Pathname:/Users/user/Products/app_name/vendor/assets/stylesheets>,
 #<Pathname:/Users/user/Products/app_name/vendor/assets/javascripts>]

Rubyのppコマンドはオブジェクトをみやすく出力するライブラリです。

手動コンパイル

アセットパイプラインのコンパイルを手動で行うことができます。

$ RAILS_ENV=production rails assets:precompile

このコマンドを実行すると、デフォルトでpublic/assets/ディレクトリ配下にコンパイルされたアセットファイルが配置されます。

デプロイツールにCapistranoを使っている場合、Capistranoを適切に構成していればコンパイルは自動化されているので、本番環境でこのコマンドを実行する必要はありません。Capistranoの以下の設定を確認してください。

Capfile

require "capistrano/rails/assets"

CapistranoのGitHubリポジトリを確認すると、以下のようにコマンド実行が実装されていることが確認できます。

capistrano/rails/assets.rb

load File.expand_path("../../tasks/assets.rake", __FILE__)

capistrano/tasks/assets.rake

    task :precompile do
      on release_roles(fetch(:assets_roles)) do
        within release_path do
          with rails_env: fetch(:rails_env), rails_groups: fetch(:rails_assets_groups) do
            execute :rake, "assets:precompile"
          end
        end
      end
    end

また、Herokuにデプロイする場合はビルドプロセスの一環でアセットファイルのコンパイルも行われるので、こちらも手動で実行する必要はありません。

$ git push heroku master

remote: -----> Preparing app for rails asset pipeline
remote:        Running: rake asset:precompile
...
remote:        Asset precompilation completed (2.76s)

まとめ

アセットパイプラインはRailsアプリに標準導入されている上、ほとんどの場合は特に設定を変更しなくても問題なく動くので、詳しい仕組みまで理解している人はあまり多くないんじゃないかと思います。しかし、アセットパイプラインの仕組みはRailsアプリの高速化という観点からはなくてはならないものであり、縁の下の力持ち的な働きをしてくれる優れた機能です。

本記事を参考にして、アセットパイプラインについて理解していただければと思います。

関連記事

【Rails】Paranoiaを使用した論理削除(ソフトデリート)
# はじめに Paranoiaは、Railsアプリケーションで論理削除(ソフトデリート)を実現するためのGemです。 論理削除は、データベースのレコードを物理的に削除するのではなく、削除フラグを設定することで「削除済み」とみなす方法です。こ [...]
2024年7月20日 21:33
【Rails】activerecord-multi-tenantを使用したマルチテナントアプリケーションの作成
# はじめに マルチテナントアプリケーションでは、複数の顧客(テナント)が同じアプリケーションを利用するため、データの分離が必要です。 activerecord-multi-tenantは、このようなマルチテナント環境をサポートするための便 [...]
2024年7月18日 16:50
【Rails】RubyとRailsにおけるattr_reader, attr_writer, attr_accessorの概念と使用方法
# はじめに RubyとRailsの開発において、`attr_reader`,`attr_writer`,`attr_accessor`は非常に便利なメソッドです。これらは、クラス内でインスタンス変数に対するゲッターおよびセッターメソッドを簡単に [...]
2024年7月17日 18:11
【Rails】RubyとRailsにおけるyieldの概念と使用方法
# はじめに RubyとRailsにおける`yield`は、メソッドやテンプレートの中で動的にコードブロックを実行する能力を提供し、これによってコードの再利用性と拡張性が大幅に向上します。本記事では、RubyとRailsにおける`yield`の概 [...]
2024年7月17日 13:15
【Rails】AASMを使用してオブジェクトの状態遷移を効率的に管理
# はじめに Railsアプリケーションにおいて、オブジェクトの状態管理は重要な課題の一つです。AASM (Acts As State Machine) gemは、複雑な状態遷移を効率的に管理します。本記事では、AASMの基本的な使い方を解説して [...]
2024年7月16日 18:00
【Rails】RSpec + Swagger + rswagでアプリケーションのAPIをテストおよびドキュメント化する方法
# はじめに Railsアプリケーションの開発において、APIのテストとドキュメント化は重要な要素です。 RSpecはテストフレームワークとして広く利用されており、SwaggerはAPIの設計とドキュメント化を支援します。これらを統合するr [...]
2024年7月16日 14:27
【Rails】mailcatcherを使用して開発環境でメール送信をテストする方法
# はじめに mailcatcherは、開発環境でのメール送信をキャプチャするためのツールです。ローカルで送信されたメールをブラウザ上で簡単に確認できるようにします。mailcatcherをRailsアプリケーションで使用する方法について説明しま [...]
2024年7月15日 16:37
【Rails】impressionistを使用してページビューやクリック数を追跡する方法
# はじめに impressionist Gemを使用してRailsアプリケーションでページビューやクリック数を追跡する方法について説明します。 # 実装方法 ## impressionist Gemのインストール まず、impre [...]
2024年7月15日 14:18