アセットパイプライン

このガイドでは、アセットパイプラインについて説明します。 このガイドを読むことで、次の事が学べるはずです。

  • アセットパイプラインとは何か、何が出来るのか
  • アプリケーションのアセットの適切な構成について
  • アセットパイプラインを使用するメリット
  • パイプラインにプリプロセッサを追加する方法
  • アセットをgemパッケージにする方法

1. アセットパイプラインとは?

アセットパイプラインは、javaScriptとCSSの連結・圧縮をフレームワークとつなぎ合わせる機能を提供します。 CoffeeScript、Sass、ERBのような他の言語を、これらのアセットに書き出す機能も備えています。

Railsの主機能であるアセットパイプラインによって、全ての開発者が、 主ライブラリの1つであるSprocketsの連結・圧縮によって、プリプロセッサの恩恵に預かることが出来ます。 RailsConf 2011でのDHH氏の講演で、Railsの「"fast by default"」方針の概要の一部として紹介されました。

アセットパイプラインは、デフォルトで有効になっています。 config/application.rbの、Applicationクラス内の下記の行でこれを無効にすることが可能です。

config.assets.enabled = false

新しいアプリケーションを作る際に、--skip-sprocketsオプションを指定することで、 アセットパイプラインを無効にすることも可能です。

rails new appname --skip-sprockets

アセットパイプラインを避ける特別な理由がない限り、 新しいアプリケーションを作る際は、デフォルトで使用するようにすべきです。

1.1 主機能

パイプラインでまず挙げられる機能は、アセットの連結です。 これは、Webページをブラウザで描画する際にリクエスト数を減らすことに繋がるため、本環境下で非常に重要になります。 Webブラウザは並列で送ることの出来るリクエスト数が限られているため、 リクエスト数を減らすことは、アプリケーション読み込みの高速化に繋がります。

Rails2.xでは、javaScriptのファイル同士、CSSのファイル同士を連結する機能は、 javascript_include_tagstylesheet_link_tagの最後に、 cache: trueを指定することで実現していました。 ただし、これにはいくつか制限がありました。 例えば、事前にキャッシュを作成する機能が無く、 またサードパーティ製のライブラリによって提供されるアセットを、そのまま含める事が出来ません。

Rails 3.1から、Railsはデフォルトで全てのjavaScriptファイルを1つの.jsファイルに、 また、全てのCSSファイルを1つの.cssファイルに連結するようになりました。 このガイドで学べば、好みに合わせてファイルのグルール化をカスタマイズ出来るようになります。 本環境下では、RailsはWebブラウザのキャッシュ機能のために、 各ファイルの名前に識別文字列となるMD5のフィンガープリントを挿入します。 このキャッシュを無効にするために、ファイル内容を編集する度にRailsは自動的にフィンガープリントを差し替えてくれます。

アセットパイプラインの第2の機能に、アセットの縮小・圧縮があります。 CSSファイルであれば、コメントと空白を削除します。 javaScriptファイルであれば、更に複雑な処理が適用されます。 オプションで構築セットから選択するか、独自のものを指定することが可能です。

アセットパイプラインの3つ目の機能に、高水準言語のコーディングを可能にし、 コンパイルによって実際に使用されるアセットにプリコンパイルしてくれます。 サポートされる言語として、CSSのためのSass、javaScriptのためのCoffeeScript、 そして両方のためにERBがデフォルトで含まれています。

1.2 何が識別文字列となり、何に気を配るべきか?

フィンガープリントは、ファイルの内容をファイル名に依存させるテクニックです。 ファイル内容が変更されると、ファイル名も同様に変更されます。 静的または滅多に更新しない内容でも、この方法であれば、異なるサーバ、異なる開発時期をまたいで、 2つの異なるバージョンのファイルの識別を容易にしてくれます。

ファイル名がその内容を元に一意である時、HTTPヘッダーは何処にあるファイル(CDN、ISP、ネットワーク、Webブラウザ内)であれ、 その内容のコピーを保持するように設定を促します。 内容が更新された際に、フィンガープリントは変更され、 これはリモートのクライアントに対し、新しい内容のコピーをリクエストすることを促すことになります。 これは一般的に、"cache busting"として知られています。

Railsはこのフィンガープリントを、通常はファイル名の末尾にハッシュ文字列を挿入することで実現しています。 例えば、global.cssというCSSファイルであれば、その内容のMD5ダイジェストで下記のように名前を変更します。

global-908e25f4bf641868d8683022a5b62f54.css

Railsアセットパイプラインによって、この方針が採択されました。

Railsの古い方針では、組み込みのヘルパーによって各アセットに日付ベースの文字列のクエリーを追記していました。 ソース内に、下記のようなコードを出力していました。

/stylesheets/global.css?1309495796

このクエリー文字列を使った方針には、いくつか不都合な点があります。

1. クエリパラメータによるファイル名の違いだけで、キャッシュ内容を当てにすることが出来ない
Steve Souders は、クエリー文字列によるキャッシュは避けることを勧めています。 彼は、この方法ではリクエストの5-20%がキャッシュされないことを調べあげました。 また、クエリー文字列による特定は、いくつかのCDNでは全く機能しません。
2. ファイル名は複数のサーバー環境のノード間で変更可能
Rails2.xのデフォルトのクエリー文字列は、ファイルの編集時間が元になります。 アセットがある環境にデプロイされた際に、タイムスタンプが同じになる保証はなく、 その結果、サーバーに応じて異なる値が使用されることになります。
3. 無駄の多い再キャッシュ
静的なアセットは新しいリリースコード毎にデプロイされ、これらのファイルの全てのmtime(最終編集日)が変更されると、 内容が変更されていないにも関わらず、リモートクライアントに再度キャッシュを取りにこさせることを強制させてしまいます。

フィンガープリントは、クエリー文字列によるこうした問題を解決し、 ファイル名が各内容に対して、一貫して依存していることを保証します。

フィンガープリントはデフォルトで、本環境下で有効となり、他の環境下では無効になります。 config.assets.digestの設定を変更することで、有効・無効を任意に変更することが可能です。

参照:

2. アセットパイプラインの使用方法

Railsの以前のバージョンでは、全てのアセットはimagesjavascriptsstylesheetsのように、 publicのサブディレクトリに配置されていました。 アセットパイプラインでは、これらのアセットをapp/assetsディレクトリ内に配置するようにしています。 このディレクトリ内のファイルは、sprocketsのGem内に含まれるSprocketsミドルウェアによって提供されます。

アセットは、まだpublic階層内に配置することも可能です。 public内のアセットは、アプリケーションまたはWebサーバーによって静的ファイルとして提供されます。 これらのファイルにプリプロセスを施さなければいけないのであれば、 app/assetsを使用すべきです。

本環境下では、デフォルトでRailsはこれらのファイルをpublic/assetsにプリコンパイルします。 このプリコンパイルされたコピーは、Webサーバーの静的アセットとして提供されます。 app/assets内のファイルは、本環境下で直接提供される事はありません。

2.1 特別なアセットを制御する

スキャフォールド、またはコントローラーを生成した際に、 RailsはjavaScriptファイル(Gemfile内でcoffee-railsのgemが指定されていれば、CoffeeScriptファイル)と、 CSSファイルも(Gemfile内でsass-railsのgemが指定されていれば、SCSSファイル)を、 コントローラーのために生成します。

例えば、ProjectsControllerを生成すると、 Railsは新しいapp/assets/javascripts/projects.js.coffeeファイル、 また他にもapp/assets/stylesheets/projects.css.scssを追加します。 デフォルトでは、これらのファイルはrequire_treeを使用して指定された場所から使用され始めます。 require_treeの詳細については、マニフェストファイルと指定を参照して下さい。

<%= javascript_include_tag params[:controller]><%= stylesheet_link_tag params[:controller]>のようにして、 コントローラー毎に独自のCSS、javaScriptファイルを読み込むことも可能です。 require_tree指定をしない場合、アセットを何度も読み込むように指定する必要があります。

アセットのプリコンパイルを使用する(本環境ではデフォルト)場合、 コントローラーのアセットが各ページで読み込まれる際に、プリコンパイルされている必要があります。 デフォルトでは、.coffee.scssファイルは、プリコンパイルされません。 開発環境下であれば、これらのファイルはその場でコンパイルされるため、正常に動作します。 ただし、本環境ではデフォルトでは、その場でのコンパイルが有効ではないため、500エラーになります。 プリコンパイルの動作については、アセットのプリコンパイルを参照してください。

CoffeeScriptを使用するためのランタイムをサポートするExecJSが必要になります。 もし、Mac OS X、またはWindowsを使用していれば、既にjavaScriptランタイムがインストールされてるはずです。 ExecJSのドキュメントで、 サポートされているjavaScriptランタイムを確認してください。

config/application.rbに下記のコードを追加することで、 コントローラーが生成される際にアセットファイルの生成を無効にすることも可能です。

config.generators do |g|
  g.assets false
end

2.2 アセットの構成

プリコンパイルのアセットは、 app/assetslib/assetsvendor/assetsの3つのうちのいずれかに置くことが出来ます。

app/assets
画像、javaScript、CSSなどのアプリケーション固有のアセットを格納します。
lib/assets
自身(または自分達)のライブラリのコードを格納します。 これは、固有のアプリケーションだけでなく、他のアプリケーションでも共有されるライブラリを対象とします。
vendor/assets
javaScriptプラグインやCSSフレームワークのような、外部のサードパーティ製のアセットを格納します。

2.2.1 パスの検索

ファイルがマニフェストまたはヘルパーによって参照される際に、 Sprocketsは3つのデフォルトの場所からアセットを探します。

デフォルトでは、app/assetsのサブディレクトリにimagesjavascriptsstylesheetsがあり、他の3つのアセットの格納場所でも同様です。 ただし、これらのサブディレクトリが特別というわけでは無く、assets/*のパスが検索対象となります。

例えば、これらのファイルがある場合、

app/assets/javascripts/home.js
lib/assets/javascripts/moovinator.js
vendor/assets/javascripts/slider.js
vendor/assets/somepackage/phonebox.js

マニフェストを次のように書くことで、参照します。

//= require home
//= require moovinator
//= require slider
//= require phonebox

また、サブディレクトリ内のアセットにもアクセスすることが可能です。

app/assets/javascripts/sub/something.js

上記は次のようにして、参照します。

//= require sub/something

Railsコンソール内で、Rails.application.config.assets.pathsを調べることで、 検索パスを確認することが可能です。

標準のassets/*に加え、追加の(完全修飾)パスをconfig/application.rb内で、 パイプラインに追加することが可能です。 例えば、下記のように指定します。

config.assets.paths << Rails.root.join("lib", "videoplayer", "flash")

パスは、検索パスで該当した順に参照されます。 デフォルトでは、app/assetsが優先され、 libvenderに一致するパスがその後に続いて参照されることになります。

マニフェスト外で参照したいファイルは、プリコンパイルの配列に追加しなければ、 本環境下で利用出来なくなる事に注意して下さい。

2.2.2 インデックスファイルの使用

Sprocketsは、特別な目的のためにindex(該当する拡張子を持つ)と名付けられたファイルを使用します。

例えば、もし多くのモジュールのjQueryライブラリを所有している場合、 それはlib/assets/(モジュール名)に格納し、 lib/assets/(モジュール名)/index.jsのファイルは、 このライブラリ内で全てのファイルに対してのマニフェストを提供します。 このファイルは、必要とする全てのファイルのリスト、または単純にrequire_treeの指定を含みます。

ライブラリ全体として、マニフェストを次のようにして、アプリケーションからアクセスさせることが可能です。

//= require library_name

これは、個別に含むファイルを指定するの代わりに、関連するコードのグループ化を行うことによって、 メンテナンスを簡素化して、クリーンな状態を維持します。

2.3 アセットへのリンクのコーディング

Sprocketsは、アセットにアクセスするための新しいメソッドを追加するような事はしません。 引き続き、javascript_include_tagstylesheet_link_tagを使用します。

<%= stylesheet_link_tag "application" %>
<%= javascript_include_tag "application" %>

通常、ビューから画像には、次のようにassets/images内の画像にアクセスします。

<%= image_tag "rails.png" %>

パイプラインがアプリケーション内(そして現在の環境下で無効になっていない)で有効になっていれば、 このファイルはSprocketsによって処理されます。 もし、ファイルがpublic/assets/rails.pngに存在すれば、 それはWebサーバによって提供されます。

代わりに、public/assets/rails-af27b6a414e6da00003503148be9b409.pngのような、 MD5ハッシュ付きのファイルにリクエストを送る場合も、同じ方法で処理されます。 ハッシュの生成については、このガイドで後述する本環境のセクションで説明します。

Sprocketsはまた、アプリケーション標準のパスとRailsエンジンによって追加されたパスを含め、 config.assets.paths内で指定されているパスも探します。

必要であれば、画像もサブディレクトリを使った構築が可能で、 tagのメソッで内で、そのディレクトリ名を指定することで、アクセスすることが可能です。

<%= image_tag "icons/rails.png" %>

もし、アセットをプリコンパイルする際に(後述の「開発環境下」を参照)、 アセットへのリンク先に何もないと、呼び出したページ内で例外が発生します。 これには、空文字列のリンクも含まれます。 そのため、ユーザー入力のデータを元にしたimage_tagなどのヘルパーを使用する際には、十分注意してください。

2.3.1 CSSとERB

アセットパイプラインは、自動的にERBを評価します。 これは、もしerb付きの拡張子をCSSアセットに追加して(例えば、application.css.erb)、 asset_pathのようなヘルパーが、CSSルールの中で利用可能になることを意味します。

.class { background-image: url(<%= asset_path 'image.png' %>) }

これは、参照される特定のアセットのパスを書き出します。 この例では、画像はアセットのパスの中の画像であるとみなれされ、 app/assets/images/image.pngのようにして参照されます。 もし、この画像が既にpublic/assets内でフィンガープリントとして利用可能であれば、 パスはそちらが参照されます。

もし、data URIを使用したいのであれば、 (CSSファイル内に直接画像データを埋め込む手法) asset_data_uriでそれを行うことが出来ます。

#logo { background: url(<%= asset_data_uri 'logo.png' %>) }

これは、CSSソース内に正しいフォーマットでdata URIを挿入します。

Rubyの閉じタグに-%\u003Eを使用出来ないことに注意してください。

2.3.2 CSSとSass

アセットパイプラインを使用している場合、アセットへのパスは書き直す必要があり、 sass-railsは、アセットクラスのimage、font、video、audio、javaScript、CSS用に、 -url-pathヘルパー(Sassならハイフン、Rubyならアンダースコア)を提供します。

  • image-url("rails.png")は、url(/assets/rails.png)になります。
  • image-path("rails.png")は、"/assets/rails.png"になります。

より一般的な形式で、アセットパスとクラスの両方を指定する方法もあります。

  • asset-url("rails.png", image)は、url(/assets/rails.png)になります。
  • asset-path("rails.png", image)は、"/assets/rails.png"になります。

2.3.3 JavaScript/CoffeeScriptとERB

もし、javaScriptアセットにerb拡張子を追加してapplication.js.erbのようにした場合、 javaScript内でasset_pathヘルパーを使用することが出来ます。

$('#logo').attr({
  src: "<%= asset_path('logo.png') %>"
});

これは、参照されるアセットのパスを出力します。

同様に、erb拡張子のCoffeeScriptファイル(例:application.js.coffee.erb)でも、 asset_pathを使用することが可能です。

$('#logo').attr src: "<%= asset_path('logo.png') %>"

2.4 マニフェストファイルとディレクティブ

Sprocketsはマニフェストファイルを使用することで、読み込んで処理を行うアセットを決定します。 これらのマニフェストファイルはディレクティブを含みます。 ディレクティブはSprocketsにどのファイルが必要で、 どの順番で一つのCSSまたはjavaScriptにまとめるかを指示します。 ディレクティブによって、Sprocketsは指定されたファイルを読み込み、必要であればそれらを1つのファイルに連結、圧縮の処理を行います。 (もし、Rails.application.config.assets.compressがtrueであれば) 多くのファイルでは無く、1つのファイルを提供することで、ブラウザのリクエスト数減らし、 ページの読み込み時間を大幅に減らすことが出来ます。 また、圧縮もファイルサイズを減らすため、ブラウザによるダウンロード時間を短縮してくれます。

例えば、新しくRailsアプリケーションを作るとデフォルトで、 app/assets/javascripts/application.jsファイルが含まれており、 下記の行が記述されています。

// ...
//= require jquery
//= require jquery_ujs
//= require_tree .

javaScriptファイルでは、ディレクティブは//=から始まります。 このケースでは、requirerequire_treeのディレクティブが使用されています。 requireディレクティブは、Sprocketsに必要とするファイルを教える際に使用します。 ここでは、jquery.jsjquery_ujs.jsが指定されていて、 Sprocketsのための検索パス内であれば、どこでも利用可能になります。 必ずしも、拡張子を明確にする必要はありません。 Sprocketsは、.jsファイル内での指定であれば、.jsファイルが必要とされているとみなします。

require_treeディレクティブは、Sprocketsに指定したディレクトリ内から、 再帰的に全てのjavaScriptファイルを対象として、出力するように指示します。 これらのパスはマニフェストファイルと相対的に指定されなければいけません。 また、require_directoryディレクティブを使用することも可能で、 これは再帰的にではなく、指定したディレクトリ内のjavaScriptファイルのみを対象とします。

ディレクティブは上から下へ処理されますが、require_treeによって含まれるファイルの順番は不定です。 そのため、特定の順でファイルが読み込まれる事を、当てにするべきではありません。 もし、いくつかの特定のjavaScriptを他のグループ化されるファイルより先に読み込む必要がある場合、 マニフェストファイルの最初に、先に読み込むファイルをrequireで指定します。 require関連のディレクティブは、出力の際に同じファイルを繰り返し出力する事を防ぐことに注意してください。

Railsはまた、デフォルトで下記の行が含まれたapp/assets/stylesheets/application.cssのファイルを作成します。

/* ...
*= require_self
*= require_tree .
*/

javaScriptファイル内で動作するディレクティブは、CSS内でも動作します。 (javaScriptファイルというよりも、明らかにスタイルシートを含むもの) CSSマニフェスト内のrequire_treeディレクティブは、javaScriptと同じように動作し、 カレントディレクトリ内の全てのスタイルシートを読み込みます。

この例では、require_selfが使用されています。 これは、require_selfが呼び出されたファイル内の、(もし、あれば)正にその場所にCSSを出力します。 もし、require_selfが複数回呼び出された場合、最後の呼び出しが適用されます。

複数のSassファイルを使用したい場合は、Sprocketsのここで紹介したディレクティブの代わりに、 通常はSassの@importルールを使用すべきです。 Sprocketsのディレクティブで全てのスコープ内のSassファイルを対象にする方法ですと、 作成される変数、ミックスインはそれが定義されたドキュメント内でしか有効になりません。

必要な分だけ、マニフェストファイルは作ることが出来ます。 例えば、admin.cssadmin.jsマニフェストは、 アプリケーションの管理者機能で使用するjsとcssファイルを含むといった事も出来ます。

同じく順序についても補足しておきます。(翻訳に自信無し) 個々にファイルを指定することで、指定された順にコンパイルされる事が可能です。 例えば、あなたは3つのCSSファイルを下記のようにして連結させるかもしれません。

/* ...
*= require reset
*= require layout
*= require chrome
*/

2.5 プリプロセス

アセットでのファイル拡張子は、どのプリプロセスを適用するかを決定します。 デフォルトのRailsのGemsetでは、コントローラーまたはスキャフォールドは、 CoffeeScriptファイルとSCSSファイルを標準のjavaScriptとCSSファイルとして生成します。 例として、予め"projects"と呼ばれるコントローラーは、app/assets/javascripts/projects.js.coffeeと、 app/assets/stylesheets/projects.css.scssファイルを生成しているとします。

これらのファイルがリクエストされると、coffee-scripsassのgemによるプロセッサーにより処理され、 ブラウザにそれぞれjavaScriptとCSSとして送り返されます。

他の拡張子を追加することによって、プリプロセスを重ねて処理するように要求する事が可能で、 拡張子の右から左に処理されます。 これらは、処理を適用すべき順に並べるべきです。 例えば、app/assets/stylesheets/projects.css.scss.erbのスタイルシートがある場合、 最初の処理はERB、次はSCSS、そして最終的にCSSが提供されます。 同様にjavaScriptファイルも、app/assets/javascripts/projects.js.coffee.erbであれば、 ERB、CoffeeScript、の順に処理され、javaScriptとして提供されます。

これらのプリプロセスの順番は、重要なので注意してください。 例えば、app/assets/javascripts/projects.js.erb.coffeeというjavaScriptファイルがあった場合、 最初にCoffeeScriptインタプリタによって処理されますが、ERBの書式が理解出来ないため、実行した際に問題が発生します。

3. 開発環境での使用

開発環境下では、アセットはマニフェストファイルに指定された順で、別々のファイルとして提供されます。

このようなapp/assets/javascripts/application.jsの場合、

//= require core
//= require projects
//= require tickets

下記のHTMLが生成されます。

<script src="/assets/core.js?body=1"></script>
<script src="/assets/projects.js?body=1"></script>
<script src="/assets/tickets.js?body=1"></script>

bodyパラメータは、Sprocketsによって必要とされます。

3.1 デバッグのOFF

config/environments/development.rbに含まれる下記の部分を更新することで、 デバッグモードをOFFにすることが出来ます。

config.assets.debug = false

デバッグモードをOFFにすると、Sprocketsは全てのファイルを連結し、必要なプリプロセスを実行します。 デバッグモードをOFFにすることで、出力されるHTMLは前述したものの代わりに下記のようなHTMLを出力します。

<script src="/assets/application.js"></script>

サーバの起動後の最初のリクエストで、アセットはコンパイルされ、キャッシュされます。 Sprocketsは、Cache-ControlのHTTPヘッダにmust-revalidateを設定することで、 ブラウザは304(Not Modified)を受け取ることになり、後続のリクエストによるオーバーヘッドを減らします。

マニフェストに指定されている、いずれかのファイルが次のリクエストまでの間に変更されると、 サーバーは新しくコンパイルしたファイルを返します。

Railsのヘルパーメソッドで、デバッグモードを有効にすることも可能です。

<%= stylesheet_link_tag "application", debug: true %>
<%= javascript_include_tag "application", debug: true %>

デバッグモードがONになっているなら、:debugオプションを使用するのは冗長です。

開発環境であっても動作確認のために圧縮を有効にしたい場合があるかもしれません。 必要に応じてオプションを指定することで、デバッグモードをOFFにしてこの確認を行います。(翻訳に自信なし)

4. 本環境での使用

本環境でのRailsは、上記で説明したスキームをフィンガープリントを使用して出力します。 デフォルトでRailsは、アセットはプリコンパイル済みであるとみなし、 Webサーバーから静的なアセットとして提供されます。

プリコンパイル中に、そのファイルの内容からMD5が生成され、 それが出力されるファイル名に差し込まれます。 これらのフィンガープリントの名前はマニフェスト名に代わって、Railsヘルパーによって出力されます。

<%= javascript_include_tag "application" %>
<%= stylesheet_link_tag "application" %>

上記は、下記のように出力されます。

<script src="/assets/application-908e25f4bf641868d8683022a5b62f54.js"></script>
<link href="/assets/application-4dd5b109ee3439da54f5bdfd78a80473.css" media="screen" rel="stylesheet" />

注意:アセットパイプラインの:cache:concatオプションは、使用されないものになったので、 javascript_include_tagstylesheet_link_tagから、削除してください。

フィンガープリントの動作は、Railsのconfig.assets.digest設定によって制御されます。 (本環境下ではtrue,それ以外ではfalseです。)

特別な事情が無ければ、デフォルトのオプションは変更すべきではありません。 もし、ファイル名が変更されず、HTTPヘッダーのキャッシュの期限が長く設定されていたら、 ファイル内容が変更されても、リモートクライアントはそれを知ることが出来ません。

4.1 アセットのプリコンパイル

Railsには、マニフェストと関係するファイルをコンパイルして出力するrakeタスクの機能が備わっています。

コンパイルされるアセットは、config.assets.prefixによって指定された場所に書きだされます。 デフォルトで、public/assetsディレクトリが指定されています。

デプロイ時に、このタスクをサーバ上で呼び出すことで、直接コンパイル済みのアセットを作成することが出来ます。 次は、コンパイルの方法について確認していきましょう。

(本環境で)プリコンパイルを実行するrakeタスクは、下記のとおりです。

$ RAILS_ENV=production bundle exec rake assets:precompile

アセットのプリコンパイルをより早くするために、 config/application.rb内のconfig.assets.initialize_on_precompileを、 falseに設定することで、部分的にアプリケーションを読み込むことが可能になります。 ただし、これを行うとテンプレートはアプリケーションオブジェクト、またはメソッドを参照することが出来ません。(翻訳に自信なし) Herokuでは、これをfalseに設定する必要があります。

もし、config.assets.initialize_on_precompilefalseに設定した場合、 デプロイ前にrake assets:precompileを実行して問題が無いか確認してください。 アセットがアプリケーションのオブジェクトまたはメソッドを参照していると、 このフラグの値に関わらず、開発環境下であっても、 その不具合の有無を明らかにしてくれるかもしれません。 このフラグの変更は、Railsエンジンにも影響します。 エンジンは、同様にプリコンパイルのアセットを定義することが可能です。 完全な環境が読み込まれないと、エンジン(または、他のGem)は読み込まれず、アセットの欠損が起こります。

Capistrano(v2.8.0とそれ以降)では、デプロイでの、この事についての処方箋(?)が含まれています。 次の行をCapfileに追加してください。

load 'deploy/assets'

これは、config.assets.prefix内で指定されたフォルダをshared/assetsにリンクします。 もし、既にこの共有(schared/assets)フォルダを使用しているのであれば、 独自のデプロイタスクを記述する必要があります。

リモートのキャッシュページは、まだキャッシュページにとっては必要な古いコンパイル済みアセットを参照するので、 このフォルダがデプロイ時に共有される事が重要になります。

もし、ローカル上でアセットをプリコンパイルしているのであれば、サーバ上でbundle install --without assetsを使用して、 アセット用のGem(Gemfile内のアセットグループのgem)のインストールを避ける事が可能です。

デフォルトのマッチャーはコンパイルするためのファイルに、pplication.jsapplication.cssと、 全てのjs、cssでは無いファイルを含みます。(これは画像アセットを自動的に全て含めます。)(翻訳に自身なし)

[ Proc.new { |path| !%w(.js .css).include?(File.extname(path)) }, /application.(css|js)$/ ]

マッチャー(と、その他のプリコンパイル配列のメンバー:下記を参照)は、最後にコンパイルしたファイル名を適用します。 これは、JS/CSSを除いた 例えば、.coffee.scssファイルは、JS/CSSへのコンパイルに自動的には含まれません。 The matcher (and other members of the precompile array; see below) is applied to final compiled file names. This means that anything that compiles to JS/CSS is excluded, as well as raw JS/CSS files; for example, .coffee and .scss files are not automatically included as they compile to JS/CSS.

もし、他のマニフェストがある、または個別のCSSとjavaScriptファイルを含めるのであれば、 config/application.rb内のprecompile配列に、それを追加することが出来ます。

config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js']

また、下記のように全てのアセットをプリコンパイルすることを選ぶことも可能です。

# config/application.rb
config.assets.precompile << Proc.new do |path|
  if path =~ /.(css|js)z/
    full_path = Rails.application.assets.resolve(path).to_path
    app_assets_path = Rails.root.join('app', 'assets').to_path
    if full_path.starts_with? app_assets_path
      puts "including asset: " + full_path
      true
    else
      puts "excluding asset: " + full_path
      false
    end
  else
    false
  end
end

これは、SassまたはCoffeeScriptファイルをprecompile配列に追加したいとしても、 常に最後がjsかcssで終わるコンパイル済みのファイル名を指定します。

rakeタスクはまた、全てのアセットをのリストと各フィンガープリントを含むmanifest.ymlも生成します。 典型的なマニフェストファイルは下記のようになります。 これは、Sprocketsにマッピングのリクエストを送り返すのを避けるためにRailsヘルパーのメソッドに使用されます。

---
rails.png: rails-bd9ad5a560b5a3a7be0808c5cd76a798.png
jquery-ui.min.js: jquery-ui-7e33882a28fc84ad0e0e47e46cbf901c.min.js
jquery.min.js: jquery-8a50feed8d29566738ad005e19fe1c2d.min.js
application.js: application-3fdab497b8fb70d20cfc5495239dfc29.js
application.css: application-8af74128f904600e41a6e39241464e03.css

マニフェストが置かれるデフォルトの場所は、config.assets.prefix('assets'がデフォルト)に指定されている場所のルートになります。

本環境で、もしプリコンパイル済みのファイルが見つからなければ、必要となるファイル名を示す Sprockets::Helpers::RailsHelper::AssetPaths::AssetNotPrecompiledError例外が発生します。

4.1.1 Far-future Expires ヘッダー

プリコンパイル済みのアセットがファイルシステム上に存在し、Webサーバーから直接それが提供されます。 これらはデフォルトでは、far-futureヘッダーが付かず、フィンガープリントのメリットを得られないので、 サーバー設定を下記を追加して、更新する必要があります。

Apacheなら、

# The Expires* directives requires the Apache module `mod_expires` to be enabled.
<Location /assets/>
  # Use of ETag is discouraged when Last-Modified is present
  Header unset ETag
  FileETag None
  # RFC says only cache for 1 year
  ExpiresActive On
  ExpiresDefault "access plus 1 year"
</Location>

nginxなら、

location ~ ^/assets/ {
  expires 1y;
  add_header Cache-Control public;

  add_header ETag "";
  break;
}

4.1.2 GZip圧縮

ファイルがプリコンパイルされる際に、Sprocketsはgzip(.gz)圧縮版のアセットを作成することも可能です。 Webサーバーは通常、適度な比率の圧縮を行うように設計されていますが、 プリコンパイルが行われていれば、Sprocketsは転送するデータ容量を最小に減らすために、最大の比率で圧縮を行います。 一方、非圧縮のファイルを圧縮するのではなく、 Webサーバーから圧縮されたコンテンツを直接ディスクから提供するように設定することも可能です。

Nginxは、gzip_staticを有効にすることで、これを自動的に行うことが可能になります。

location ~ ^/(assets)/  {
  root /path/to/public;
  gzip_static on; # to serve pre-gzipped version
  expires max;
  add_header Cache-Control public;
}

この機能を提供するコアモジュールがWebサーバー上でコンパイルされていれば、このディレクティブが使用可能になります。 Ubuntuパッケージは、nginx-lightモジュールもコンパイル済みです。(翻訳に自信なし) そうでなければ、手動でコンパイルを実行する必要があるかもしれません。

./configure --with-http_gzip_static_module

もし、nginxとPhusion Passengerのコンパイルをする場合、プロンプトで尋ねられた際にパスを渡す必要があります。

Apacheは豊富な設定が可能な一方、複雑になりがちなので注意が必要です。これらの情報はGoogle検索で調べてみて下さい。 (Apacheの良い設定例があれば、このガイドを更新する手助けをお願いします)

4.2 ローカルでのプリコンパイル

ある理由で、アセットをローカル上でプリコンパイルしたいというケースがあるかもしれません。 例えば、

  • 本環境下のシステムファイルへの書き込みアクセスが出来ない場合
  • 複数のサーバーでデプロイの必要があり、重複する作業を避けたい場合
  • アセットの変更を含まないが、頻繁にデプロイが必要な場合

ローカル上でコンパイルを行うことで、そのコンパイル済みのファイルをコミットして、 通常通りのデプロイを行うことが出来るようになります。

ただし、下記の2つの注意点があります。

  • Capistranoのアセットをプリコンパイルするデプロイタスクを実行してはいけません。
  • 下記2つのアプリケーション設定を変更しなければいけません。

config/environments/development.rb内に、下記の行を追加してください。

config.assets.prefix = "/dev-assets"

また、application.rb内にこの設定も必要です。

config.assets.initialize_on_precompile = false

prefixの変更は、Railsに開発環境下で、アセットを提供するURLを異なるものを使用させるようにし、 全てのリクエストをSprocketsに渡します。 この変更を行わないと、アプリケーションは開発環境下でpublic/assetsからプリコンパイル済みのアセットを提供しようとし、 再びアセットをコンパイルするまで、ローカルで行った変更を確認することが出来なくなります。

initialize_on_precompileの変更は、プリコンパイルのタスクをRailsの起動無しで実行するように指定します。 プリコンパイルのタスクは、本環境下ではデフォルトで実行し、指定したproductionデータベースへの接続を試みるからです。 ローカルでこのオプションを指定してコンパイルする際に、パイプラインのファイル内で、 Railsのリソースに依存した(データベースを使用するような)コードを書けないことに注意してください。(翻訳に自信なし)

また、任意の圧縮・連結も開発環境下で利用可能であるか確認しておく必要があります。

実際に、これでローカル上でワーキングツリーのこれらのファイルをプリコンパイルが出来るようになり、 そのファイルを必要に応じてコミットします。 開発環境は期待したとおりに動作するでしょう。

4.3 ライブコンパイル

状況によっては、ライブコンパイルを使用したいケースがあるかもしれません。 このモードは、パイプラライン内のアセットへの全てのリクエストを、Sprocketsによって直接扱われます。

有効にするには、下記のようにオプションを設定します。

config.assets.compile = true

最初のアセットへのリクエストは、上述したようにコ開発環境下では、コンパイル、キャッシュされ、 ヘルパーがマニフェスト名を使用してMD5ハッシュを含む名前に切り替えます。

Sprocketsはまた、Cache-ControlのHTTPヘッダーをmax-age=31536000に設定します。 これは、サーバーとクライアント側のブラウザの間に、そのコンテンツ(提供されたファイル)を1年間キャッシュするように指示することになります。 これは、サーバからのアセットへのリクエスト数を減らすことに繋がり、 アセットはローカルブラウザ内のキャッシュ、またはその間に介在する何らかのキャッシュになる機会を得る事になります。

このモードは、デフォルトに比べてメモリー消費が多く、パフォーマンスが低下するため、推奨されません。

本環境のアプリケーションをjavaScriptランタイムが存在しないシステム(Linux等)にデプロイする際は、 Gemfileに描きを追加する必要があるかもしれません。

group :production do
  gem 'therubyracer'
end

4.4 CDN

もし、アセットがCDNによって提供されるのであれば、開発者側がそのキャッシュについて気にする必要はなくなりますが、 これは問題を引き起こす可能性があります。 もし、config.action_controller.perform_caching = trueを使用している場合、 Rack::Cacheは、アセットの格納にRails.cacheを使用します。 これは、すぐにキャッシュ溢れを引き起こしてしまうかもしれません。

全てのキャッシュは異なるため、CDNがどのようにキャッシュを扱うのかを評価し、パイプラインで問題なく動作するか確認してください。 もしかしたら、指定した設定との関連で不都合なことがあるかもしれませんし、無いかもしれません。 例えば、nginxをデフォルトで使用する場合は、HTTPキャッシュの使用で何も問題は出ないはずです。

5. パイプラインのカスタマイズ

5.1 CSS圧縮

現在、CSS圧縮のオプションの1つにYUIがあります。 YUI Compressorは、圧縮処理を提供してくれます。

下記の行でYUI Compressorを有効にし、yui-compressorを必要とします。

config.assets.css_compressor = :yui

config.assets.compressは、CSS圧縮を有効にするために、true>にしなければいけないことに注意してください。

5.2 JavaScript圧縮

javaScriptの圧縮オプションで使用可能なものに、:closure:uglifier:yuiがあります。 それぞれ、closure-compileruglifieryui-compressorのGemが必要になります。

デフォルトで、Gemfileはuglifierを含んでいます。 このGemは、UglifyJS(Node.jsのために書かれました)をRubyでラップしています。 これを使った圧縮では、コードからホワイトスペースを除去します。 また、ifelse条件を可能であれば、三項演算子に書き換えるような最適化も含みます。

次の行は、javaScript圧縮のためのuglifierを呼び出します。

config.assets.js_compressor = :uglifier

config.assets.compressは、javaScript圧縮を有効にするために、trueにしなければいけないことに注意してください。

uglifierを使用するために、 ExecJSがサポートしているランタイムが必要になります。 もし、Mac OSXまたは、Windowsを使用しているのであれば、OSにはjavaScriptランタイムがインストールされています。 ExecJSのドキュメントで、 サポートされているjavaScriptランタイムの情報を確認してください。

5.3 オリジナルのCompressorの使用について

CompressorのCSSとjavaScriptの設定も、任意のオブジェクトで行います。 このオブジェクトは、文字列の引数を1つ受け取って、文字列を返すcompressメソッドを持たなければいけません。

class Transformer
  def compress(string)
    do_something_returning_a_string(string)
  end
end

これを有効にするために、application.rb内の設定オプションに、 newしたオブジェクトを渡してください。

config.assets.css_compressor = Transformer.new

5.4 アセットパスの変更

デフォルトでSprocketsが使用する公開パスは、/assetsです。

これをそれ以外のものに変更することが可能です。

config.assets.prefix = "/some_other_path"

このオプションは、古いプロジェクトの更新時に、例えばアセットパイプラインは使用していなかったがこのパス自体は既に使用されている、 または新しいリソースのためにそのパスを使用したいといった場合に便利です。

5.5 X-Sendfile Headers

X-Sendfileヘッダーは、アプリケーションからのレスポンスをWebサーバーに無視させるためのディレクティブで、 ディスクから指定されたファイルを代わりに提供します。 このオプションはデフォルトでOFFになっていますが、サーバーがサポートしていれば有効にすることが出来ます。 有効にすると、Webサーバーにファイルを提供する役割を渡し、それによって高速化を行います。

Apacheとnginxはこのオプションをサポートし、config/environments/production.rbで有効にすることが出来ます。

# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx

もし、既存のアプリケーションをアップグレードして、このオプションを使用するような場合、 このオプションの設定はproduction.rbと本環境の振る舞いをすると定義した環境にだけ、 ペーストしてください。(application.rbにペーストしないでください)

6. アセットのキャッシュストア

デフォルトのRailsキャッシュストアは、Sprocketsによって開発環境と本環境でアセットをキャッシュするのに使用されます。 これは、config.assets.cache_storeで変更することが可能です。

config.assets.cache_store = :memory_store

アセットのキャシュストアを受け入れるオプションは、アプリケーションのキャッシュストアの場合と同じです。(翻訳に自信なし)

config.assets.cache_store = :memory_store, { size: 32.megabytes }

7. Gemにアセットを追加する方法

アセットはまた、Gemの形式で外部のソースから提供されることもあります。

良例としてjquery-railsのGemが挙げられ、これはRailsの標準javaScriptライブラリのGemとして提供されます。 このGemは、Rails::Engineを継承したエンジンクラスを含みます。 これにより、このGemのディレクトリがアセットを含み、 このエンジンのapp/assetslib/assetsvendor/assetsディレクトリが、 Sprocketsの検索パスに追加された可能性があることをRailsに通知されます。

8. ライブラリまたはGemのプリプロセッサの作成

SprocketsはTiltを、 異なるテンプレートエンジンの共通のインターフェースとして使用するため、作成するGemにTiltのテンプレートプロトコルを実装すべきです。 通常は、Tilt::Templateのサブクラスにし、最終的な出力を返すevaluateメソッドを再実装します。 テンプレートのソースは、@codeに格納されています。 より詳しく知りたければ、Tilt::Templateのソースコードを確認してください。

module BangBang
  class Template < ::Tilt::Template
    # Adds a "!" to original template.
    def evaluate(scope, locals, &block)
      "!"
    end
  end
end

Templateクラスの準備が出来たら、テンプレートファイルの拡張子の関連付けを行いましょう。

Sprockets.register_engine '.bang', BangBang::Template

9. 古いバージョンのRailsからのアップグレード

アップグレードする際に、少し気をつけなければいけない事があります。 まず、public/から新しい場所へファイルを移動する必要があります。 ファイルの種類に応じて正しい場所に配置するためのガイダンスについては、 上述のアセットの構成を参照してください。

次にjavaScriptファイルの重複を避ける必要があります。 Rails3.1以上で、jQueryがデフォルトのjavaScriptライブラリになり、自動的に含まれるため、 app/assetsjquery.jsをコピーする必要はありません。

3つ目は、様々な環境ファイルを正しいデフォルトのオプションに更新することです。 下記の変更は、バージョン3.1.0のデフォルトを反映しています。

application.rbです。

# Enable the asset pipeline
config.assets.enabled = true

# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'

# Change the path that assets are served from
# config.assets.prefix = "/assets"

development.rbです。

# Do not compress assets
config.assets.compress = false

# Expands the lines which load the assets
config.assets.debug = true

そして、production.rbです。

# Compress JavaScripts and CSS
config.assets.compress = true

# Choose the compressors to use
# config.assets.js_compressor  = :uglifier
# config.assets.css_compressor = :yui

# Don't fallback to assets pipeline if a precompiled asset is missed
config.assets.compile = false

# Generate digests for assets URLs.
config.assets.digest = true

# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
# config.assets.precompile += %w( search.js )

test.rbを変更する必要はありません。 テスト環境のデフォルトは、config.assets.compiletrueで、 config.assets.compressconfig.assets.debugconfig.assets.digestfalseです。

また、下記もGemfileに追加する必要があります。

# Gems used only for assets and not required
# in production environments by default.
group :assets do
  gem 'sass-rails',   "~> 3.2.3"
  gem 'coffee-rails', "~> 3.2.1"
  gem 'uglifier'
end

もし、Bundlerでassetsのgroupを使用している場合、 config/application.rbに、下記のBundlerのrequireが指定されているか確認してください。

# If you precompile assets before deploying to production, use this line
Bundler.require *Rails.groups(:assets => %w(development test))
# If you want your assets lazily compiled in production, use this line
# Bundler.require(:default, :assets, Rails.env)

下記は、代わりに生成されたバージョンです。

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(:default, Rails.env)

 Back to top

© 2010 - 2017 STUDIO KINGDOM