はじめに
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
*/
アセットファイルの連結には以下のディレクティブを使うことができます。いろいろありますが、実際に使うのはrequire
、require_self
、require_tree
くらいなので、全部を覚えておく必要はありません。
ディレクティブ | 意味 |
---|---|
require |
指定したファイルを連結する。 |
require_self |
マニフェストファイル自体の内容を連結する。 |
require_directory |
指定したフォルダー内のファイルを連結する。 |
require_tree |
指定したフォルダー内のファイルを再起的に連結する。 |
link |
指定したファイルをコンパイルする(連結はしない)。 |
link_directory |
指定したフォルダー内のファイルをコンパイルする(連結はしない)。 |
link_tree |
指定したフォルダー内のファイルを再起的にコンパイルする(連結はしない)。 |
depend_on |
指定したファイルが変更された場合に再コンパイルする。 |
stub |
指定したファイルを無視する。 |
アセットファイルの圧縮
アセットファイルの連結が行われると同時に圧縮も行われます。ここで言う圧縮とは、ファイルの最小化や難読化のことを言い、ファイルをZIP形式などのアーカイブファイルにすることではありません。
CSSファイルの圧縮
CSSファイルは、ホワイトスペースやコメントを削除することで圧縮されます。CSSファイルの圧縮方式にはSass
とYUI
の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ファイルの圧縮方式にはUglifier
、YUI
、Closure
の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アプリの高速化という観点からはなくてはならないものであり、縁の下の力持ち的な働きをしてくれる優れた機能です。
本記事を参考にして、アセットパイプラインについて理解していただければと思います。