Rails Asset Pipelineがうまくいかないときの問題の切り分けかた

  • 347
    いいね
  • 1
    コメント

もう二度とasset pipelineで苦しみたくないので、問題切り分けの手順をまとめたい。
(asset pipelineのおおかた理解してる人は5->1の順で切り分けていったほうが効率的かもです)

1. assetsパスの確認(アセットパイプラインで配信したいコンテンツがassetsパスに入っているか?)

当然ですが、app/controllersdb/migrateなどの下に画像ファイルを置いても、アセットパイプラインでコンテンツ配信することはできません。
では、どこにアセットを置けば配信できるのか?
assetsパスに置く必要があります。

確認方法

アプリケーションの現在のassetsパスは以下のように確認できる

rails c
$> Rails.application.config.assets.paths
=> ["/vagrant/shared/test_app/app/assets/images",
 "/vagrant/shared/test_app/app/assets/javascripts",
 "/vagrant/shared/test_app/app/assets/stylesheets",
 "/vagrant/shared/test_app/vendor/assets/fonts",
 "/vagrant/shared/test_app/vendor/assets/images",
 "/vagrant/shared/test_app/vendor/assets/javascripts",
 "/vagrant/shared/test_app/vendor/assets/stylesheets",
 "/usr/local/ruby-2.1.1/lib/ruby/gems/2.1.0/gems/jquery-turbolinks-2.1.0/vendor/assets/javascripts",
 "/usr/local/ruby-2.1.1/lib/ruby/gems/2.1.0/gems/turbolinks-1.1.1/lib/assets/javascripts",
 "/usr/local/ruby-2.1.1/lib/ruby/gems/2.1.0/gems/jquery-rails-3.0.4/vendor/assets/javascripts",
 "/usr/local/ruby-2.1.1/lib/ruby/gems/2.1.0/gems/coffee-rails-4.0.1/lib/assets/javascripts",
 "/usr/local/ruby-2.1.1/lib/ruby/gems/2.1.0/gems/bootstrap-sass-2.3.2.0/vendor/assets/images",
 "/usr/local/ruby-2.1.1/lib/ruby/gems/2.1.0/gems/bootstrap-sass-2.3.2.0/vendor/assets/javascripts",
 "/usr/local/ruby-2.1.1/lib/ruby/gems/2.1.0/gems/bootstrap-sass-2.3.2.0/vendor/assets/stylesheets"]

修正方法

assetsパスを任意のディレクトリに追加したい場合は、Rails.application.config.assets.pathsを設定する

## 既存の設定がないか確認
grep -r 'config.assets.paths' config/
## config/application.rb
## config/initializers/assets.rb
## などのファイルに以下のように記述する
Rails.application.config.assets.paths << Rails.root.join("vendor", "original_assets", "images")
Rails.application.config.assets.paths << Rails.root.join("vendor", "original_assets", "stylesheets")
Rails.application.config.assets.paths << Rails.root.join("vendor", "original_assets", "javascripts")

2. プリコンパイル設定の確認(配信したいコンテンツがプリコンパイル対象になっているか?)

2番めに確認すべきは、配信したいアセットがマニフェストから読み込まれているか、または、プリコンパイル対象になっているか、です。

配信したいコンテンツが、

  • マニフェストから読み込まれている場合
    • マニフェストがプリコンパイル対象になっている場合 => OK
    • マニフェストがプリコンパイル対象になっていない場合 => マニフェストをプリコンパイル対象に追加する
  • アセット単体で使いたい場合
    • アセットがプリコンパイル対象になっている場合 => OK
    • アセットがプリコンパイル対象になっていない場合 => そのアセットをプリコンパイル対象に追加する

マニフェスト確認方法

マニフェストとは、はじめにrailsを作るとapp/assets/stylesheets/application.cssapp/assets/javascripts/application.jsに作成されていることでおなじみのファイルです。

vendor/original_assets/stylesheets/vendor.css
/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
 * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the top of the
 * compiled file, but it's generally better to create a new file per style scope.
 *
 *= require font-awesome
 *= require bootstrap/bootstrap
 *= require libs/bootstrap-select.min
 *= require libs/jquery.slider.min
 *= require libs/owl.carousel
 *= require libs/owl.transitions
 *= require style
 *= require_self
 */

font-awesome.css
font-awesome.css.scss
font-awesome.css.scss.erb
のいずれの形式でも、require font-awesomeと書けばOKです。
require_tree .みたいな記法でディレクトリ以下を全部読み込むこともできます。
http://guides.rubyonrails.org/asset_pipeline.html#manifest-files-and-directives

プリコンパイルについて

プリコンパイルは、複数のファイルをいっこにまとめて、圧縮して、ファイル名にフィンガープリント(ダイジェスト)をつけて、公開ディレクトリにデプロイする、などの作業と解釈してます。
プリコンパイルされていないアセットは、production環境では見ることができません。配信したいアセットがプリコンパイルの対象になっていることを確認する必要があります。
(画像やフォントなどのファイルであっても、アセットパイプラインで管理する以上、フィンガープリントをつけるなどの作業が必要なのでプリコンパイル対象にする必要があります)

プリコンパイル対象の確認方法

どのファイルがプリコンパイルの対象になるかは、ちょっと混乱気味です。
(ここを見れば一発でわかるという情報ご存知のかたがいましたら教えてください!)

  1. app/assets/以下のapplication.css
  2. app/assets/以下のapplication.js
  3. app/assets/以下のjs, css以外のアセット
  4. libs/assets/以下のjs, css以外のアセット※
  5. vendor/assets/以下のjs, css以外のアセット※
  6. 自分でassetsパスに追加したディレクトリ以下のjs, css以外のアセット※

:exclamation: 4,5,6についてはバージョンによって仕様が変更になっているようです!
https://github.com/rails/rails/pull/7968
新しいバージョンのrailsでは、4,5,6のディレクトリ以下のjs, css以外のアセットは、デフォルトではプリコンパイルの対象に入らないようです。

以上に記述したのが、デフォルトでプリコンパイルの対象に入るアセットの条件です。
デフォルトに加えてプリコンパイル対象を追加することもできます。追加されたプリコンパイル対象を以下のように確認できます。

rails c
$> Rails.application.config.assets.precompile
=> [#<Proc:0x007f8c741a2428@/usr/local/ruby-2.1.1/lib/ruby/gems/2.1.0/gems/sprockets-rails-2.2.0/lib/sprockets/railtie.rb:40 (lambda)>,
 /(?:\/|\\|\A)application\.(css|js)$/]

プリコンパイル対象の変更方法

config.assets.precompileを設定することでプリコンパイル対象を追加することができます。

## 既存の設定がないか確認
grep -r 'config.assets.precompile' config/

■プリコンパイル対象追加例

## config/application.rb
## config/initializers/assets.rb
## などのファイルに以下のように記述する

Rails.application.config.assets.precompile += %w( *.eot *.woff *.ttf *.svg *.otf *.png *.jpg *.jpeg *.gif vendor.css vendor.js )
rails c
$> Rails.application.config.assets.precompile
=> [#<Proc:0x007fbb9a556420@/usr/local/ruby-2.1.1/lib/ruby/gems/2.1.0/gems/sprockets-rails-2.2.0/lib/sprockets/railtie.rb:40 (lambda)>,
 /(?:\/|\\|\A)application\.(css|js)$/,
 "*.eot",
 "*.woff",
 "*.ttf",
 "*.svg",
 "*.otf",
 "*.png",
 "*.jpg",
 "*.jpeg",
 "*.gif",
 "vendor.css",
 "vendor.js"]

↑の設定の意図。
通常、app/assets/以下に画像やフォントファイルを配置すれば、条件3を満たすのでプリコンパイルの対象になりますが、vendor/assets/以下はプリコンパイルされない場合があるため、*.eot *.woff *.ttf *.svg *.otf *.png *.jpg *.jpeg *.gifなどの拡張子をすべて明示的に追加しています。
また、vendor.(css|js)のように、application.(js|css)以外のマニフェストを定義したい場合やjs, cssファイルを単体で使いたい場合は明示的にファイル名を書く必要があります。

ちなみに、develop環境ではプリコンパイルされていなくてもアセットを配信することができます。なので、develop環境でアセットまわりに問題が起きている場合、プリコンパイルが原因である可能性はほとんどないと思います。

:exclamation: sprockets-railsのいくつかのバージョンからはconfig.assets.digest = falseにしているとすべてのファイルがプリコンパイル対象から外れる仕様になったみたいです!
https://github.com/rails/sprockets-rails/issues/49

3. プリコンパイルの手動実行(実際にプリコンパイルされるか?)

capistranoなどでプリコンパイルを自動化していると、問題の原因がさらにわかりづらくなると思うので、そういうときはプリコンパイルを手動で実行してみるべきです。

手動実行方法

■既にプリコンパイルされたファイルを一旦すべて消す(消しても問題ないか注意しておこなってください)

bundle exec rake assets:clobber RAILS_ENV=staging
ls -la public/assets

■手動でプリコンパイルを実行する

bundle exec rake assets:precompile RAILS_ENV=staging
ls -la public/assets

プリコンパイルされたアセットが配置される先のディレクトリはカスタマイズ可能なので、想定のディレクトリにファイルがやってこない場合は以下を確認してみてもいいかもです。

rails c
$> Rails.application.config.assets.prefix
=> "/assets"
## ↑の場合、public/assets/以下がプリコンパイル済みアセットが置かれるディレクトリになります
grep -r 'config.assets.prefix' config/

この時点でアセットがpublic/assets/以下にやってこない場合は、前述の1,2のどこかに問題があるはずなので、戻って再度確認します。

4. Webサーバを通してアセットのアクセス確認(プリコンパイルされたファイルはURL経由で配信されるか?)

public/assets/以下にプリコンパイルされたファイルが配置されたら、あとはほとんどrailsとは関係がなくなります。
HTTPとの闘いに突入します。
apacheやnginxをWebサーバとして使っている場合、それらのドキュメントルートが#{Rails.root}/public}になっている必要があります。

確認方法

  1. プリコンパイル済みアセットどれか1つに対してファイル名直うちでアクセスしてみる

■URL例

http://test_app.metheglin.jp/assets/logo-c09ffc1de5270e769f699267ed93969e.png

アプリケーションを通してでなく、アセットに直接アクセスしてみるのがポイントです。
この時点でコンテンツが想定通りに表示されたらここまでは問題なしです。

  1. Webサーバのドキュメントルートが#{Rails.root}/public}になっているか

■Webサーバ設定例

grep root /usr/local/nginx-1.6.0/conf/conf.d/test_app.metheglin.jp.conf
 root   /usr/local/services/test_app/public;
## publicであることに注意!!公開ディレクトリですよ!!

■Webサーバ設定も問題なさそうな場合は、HTTPレスポンスを参考にデバッグを進めます

404 => URLのパスやファイル名が間違っていないか?
403 => ファイルのパーミッションによりアクセス権限がない?
(パーミッションの確認は、ファイルの絶対パスをすべての階層辿ってチェックすることが重要)
Webサーバ側でのアクセス権限が間違っていないか?(apacheでいうAllow/Deny/Requireなどの設定)
50X => Webサーバが落ちてないか?
その他 => basic認証などを求められてないか?など

5. HTML,CSSリンク先とプリコンパイル済みファイルのパスが一致することを確認

ここまでの確認を終えて、アセットのURL直うちだと表示できるのに、railsアプリケーションを通すと表示できないという場合は、ほぼ確実にHTML,CSSでのリンクに失敗しています。

HTMLリンクの確認方法

ブラウザを右クリックなどしてHTMLソースを確認して、
linkタグのhref属性
scriptタグのsrc属性
imgタグのsrc属性
に書かれているファイルパスがプリコンパイル済みアセットと一致するか確認します。

HTMLリンクの修正方法

フィンガープリント(config.assets.digest)を有効にしている場合、プリコンパイル後のアセットのファイル名がわからないので、stylesheet_link_tag, javascript_include_tag, image_tagなどのrailsヘルパーメソッドを使わないといけません。

CSSのurl()などで指定するリンクの確認方法

スタイルシートでもbackground: url(/assets/snow-white.png);のように画像のパスを指定するときがあります。
該当のスタイルシートにアクセスして、url()の中に記載されているファイルパスとプリコンパイル済みアセットのファイルパスが一致することを確認します。

CSSのurl()などで指定するリンクの修正方法

このパターンでフィンガープリントつきのアセットに対応するには、2種類の方法があります。

■CSSアセットがerb形式のとき

style.css.scss.erb
div {
  background: url(<%= asset_path('snow-white.png') %>);
}

■CSSアセットがscss|sass形式のとき

style.css.scss
div {
  background: asset-url('snow-white.png');
}
div {
  background: image-url('snow-white.png');
}

// どちらも↓のように出力されます
// div {
//   background: url(snow-white-e02211427b7cb5f478c197571d01b0ed.png);
// }

■CSSアセットが通常のcss形式のとき
ふつうのcss拡張子のアセットは、<%= asset_path('snow-white.png') %>asset-url('snow-white.png')image-url('snow-white.png')も解釈できないので注意です!!
つまり、.css.scssか.css.erb形式に変更する必要があります。


Environment

% ruby -v
ruby 2.1.1p76 (2014-02-24 revision 45161) [x86_64-linux]

% rails -v
Rails 4.1.1