railsのアセットパイプラインについて調べてみた。

##はじめに
railsアプリケーションにbootstrapを導入する際に、アセットパイプラインについて何もわからなかったのでに、学習した内容をここに記録します。

##アセットパイプラインとは
JavaScriptやCSSのアセットを最小化 (minify: スペースや改行を詰めるなど) または圧縮して連結するためのフレームワークです。

##アセットパイプラインの仕組みができた背景
アセットパイプラインが登場する以前（rails 3.1以前）は、publicフォルダー配下に全てのアセットファイルを置いていました。開発者は次のようなジレンマに陥っていました。

- わかりやすいようにフォルダーを細かく分割したいが、パフォーマンスのことを考えるとフォルダーは、分割しない方がいい。（リクエスト数の増加による読み込み速度の低下）
- CoffeeScript, SASS, ECMAScript 6といった、技術を使って、簡潔で読みやすいコードを書きたいが、パフォーマンスのことを考えると、オリジナルの書き方をした方がいい。（ファイルデータ容量の増大による読み込み速度の低下）

このように、開発のし易さとパフォーマンスの間には、トレードオフの関係があるという状態でした。
そこで、win-winの関係性にするべく開発されたのが、アセットパイプラインでした。

###アセットパイプラインの主な機能
アセットパイプラインには次のような機能があります。

- アセットを連結する
- アセットの最小化（一種の圧縮）
- 高級な言語を利用したコーディングのサポート
- SHA256フィンガープリントを挿入


##アセットを連結する
アセットを連結することでブラウザがWebページをレンダリングするためのリクエスト数を減らすことができます。Webブラウザが同時に処理できるリクエスト数には限りがあるため、同時リクエスト数を減らすことができればその分読み込みが高速になります。
##アセットの最小化（一種の圧縮）
CSSファイルの最小化は、ホワイトスペースとコメントを削除することによって行われます。JavaScriptの最小化プロセスはもう少し複雑です。最小化方法はビルトインのオプションから選んだり、独自に指定したりすることができます。
##高級な言語を利用したコーディングのサポート
より高級な言語で記述されたコードはプリコンパイルされ、実際のアセットになります。デフォルトでサポートされている言語は、CSSに代わるSass、JavaScriptに代わるCoffeeScript、CSS/JavaScriptに代わるERBです。
##SHA256フィンガープリントを挿入
本番環境では、※アセットプリコンパイル時に、アセットファイル名にSHA256フィンガープリントを挿入し、アセットファイルがWebブラウザでキャッシュされるようにします。このフィンガープリントを変更することでブラウザでキャッシュされていた既存のアセットを無効にすることができます。フィンガープリントの変更は、アセットファイルの内容が変更された時に自動的に行われます。
※アセットプリコンパイルについては、後述します。

##アセットプリコンパイルって？
本番環境にデプロイ時にアセットファイルをブラウザに適したファイルにコンパイルすること。
開発環境では、オンザフライで自動でコンパイルされますが、本番環境では、手動でコンパイルする必要があります。
アセットプリコンパイルは、以下のコードで実行できます。

~~~bash
$ RAILS_ENV=production bin/rails assets:precompile
~~~

###アセットプリコンパイルを実行されると、、

- 圧縮、連結されたアセットファイル名にSHA256フィンガープリントを挿入し、アセットファイルがWebブラウザでキャッシュされるようになります。
- プリプロセスにより高級言語は、コンパイルされ、処理後のファイルが※public/assetsディレクトリに置かれてRailsアプリケーションやWebサーバーによって配信されるようになります。

※publicディレクトリ以下に置かれているあらゆるアセットはアプリケーションまたはWebサーバーによって静的なファイルとして取り扱われます。

##どうして本番環境では、アセットプリコンパイルが必要なのか。
[railsガイドのアセットパイプラインのページ](https://railsguides.jp/asset_pipeline.html#production%E7%92%B0%E5%A2%83%E3%81%AE%E5%A0%B4%E5%90%88)では本番環境でのアセットの提供方法について、次のことが書かれています。
> デフォルトでは、Railsのアセットはプリコンパイル済みかつ静的なアセットとしてWebサーバーから提供されることが前提になっています。

>productionモードでは、Railsはプリコンパイルされたファイルをpublic/assetsに置きます。プリコンパイルされたファイルは、Webサーバーによって静的なアセットとして扱われます。app/assetsに置かれたファイルがそのままの形でproduction環境で利用されることは決してありません。

一方、開発環境では、次のようになります。
>developmentモードの場合、アセットは個別のファイルとして、マニフェストファイルの記載順に読み込まれます。

>developmentモードの場合、あるいはアセットパイプラインが無効になっている場合は、これらのアセットへのリクエストはsass gemが提供するプロセッサによって処理され、通常のCSSとしてブラウザへのレスポンスを送信します。

上記の記述より次のことがわかります。

- 本番環境では、アセットは、Webサーバーによって提供されるもの。
- Webサーバーは、プリコンパイル済みかつ静的なアセットしか扱わない。
- Webサーバーは、public/assetsに置かれたファイルを静的なアセットと見なす。

つまり、プリコンパイルしないとapp/assetsに置かれたファイルがpublic/assetsに置かれることはないので、本番環境でアセットファイルの内容が適用されないという訳です。

ちなみに、デプロイ時にこのタスクをwebサーバー上で呼び出すと、コンパイル済みアセットをサーバー上で直接作成できます。

###ローカルでプリコンパイルする
アセットプリコンパイルは、本番環境特有のものではありません。開発環境でアセットプリコンパイルすることもできます。
>場合によっては、productionサーバーでアセットをコンパイルしたくないことがあります。たとえば、producionファイルシステムへの書き込みアクセスが制限されている場合や、アセットを変更しないデプロイが頻繁に行われる場合などが考えられます。

>そのような場合は、アセットをローカルでプリコンパイルできます。つまり、production向けの最終的なコンパイル済みアセットを、production環境にデプロイする前にソースコードリポジトリに追加するということです。この方法なら、productionサーバーにデプロイするたびにproductionで別途プリコンパイルを実行する必要はありません。
>以下を実行すると、production向けにプリコンパイルできます。

~~~bash
$ RAILS_ENV=production rails assets:precompile
~~~

####開発環境でアセットプリコンパイルをする際の注意点
開発環境では、オンザフライでアセットファイルがコンパイルされるため、プリコンパイルせずともアセットファイルの変更内容が即時に反映されます。しかし、開発環境でアセットプリコンパイルする際に、注意しないとアセットを変更してもブラウザに反映されなくなります。

先述しましたが、プリコンパイル済みのアセットファイルは、webサーバーで配信可能な状態になります。railsでは、配信可能状態のプリコンパイル済みのファイルが存在している場合、常にそのファイルを参照します。これは、本番環境も開発環境も同様です。
デフォルトでは、本番環境と同様に、開発環境でアセットプリコンパイルすると、public/assetsフォルダーにプリコンパイル済みのアセットファイルが置かれます。そうするとrailsは、このプリコンパイル済みのファイルを参照するため、アセットファイルの変更をオンザフライでコンパイルして、適用しなくなってしまうのです。

####解決策
開発環境でアセットプリコンパイルする際は、本番環境と別のディレクトリにプリコンパイル済みのファイルを置くように設定すれば、この問題は解決できます。
この設定は、config/environments/development.rbファイルに以下の行を追加することでできます。

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

##参考
[アセットパイプライン](https://railsguides.jp/asset_pipeline.html)
[Webpacker の概要](https://railsguides.jp/webpacker.html)
[how_sprockets_works](https://github.com/rails/sprockets/blob/master/guides/how_sprockets_works.md)