今回は、Railsで JS/CSS/画像 などのフロントエンドのリソース群を管理する標準的な方法である、「アセットパイプライン」について学ぶ。
※フロントエンド周りは変化が激しく、Railsでも webpack がサポートされたりと、今後なにがデファクトになるのか正直よくわからないのだが、Rails入門としては、まずは伝統的(?)な Sprockets + CoffeeScript/SASS によるアセットパイプラインの仕組みを押さえておきたいと思う。
なお、Railsにおけるフロントエンドの動向については、以下の記事が参考になります。
Railsフロントエンド技術の今とこれから
【0】概要
JavaScript(以下JS)やCSSは、開発時には機能ごとにファイルを分けて人間が分かりやすい方法で管理し、配信時には最小化してブラウザの読み込み速度を改善させるのが一般的。
Railsでは、それを実現する方法のひとつとして、「アセットパイプライン」と呼ばれる仕組みを標準で提供している。
仕組みをざっくり整理すると、
●実装
・JSは、CoffeeScript というRubyライクなスクリプト(拡張子は .coffee )、または素のJS(拡張子は .js )で実装する
・CSSは、Sass という拡張的な記法(拡張子は .scss )、または素のCSS( .css )で実装する
・JS/CSSともに、ERB(ビューで使うような <%〜%> などによる埋め込みルビー)も使える。その場合は拡張子に「.erb」も付加しておく
・上記の「.coffee」「.js」「.scss」「.css」「*.erb」や、画像ファイルなどは、assets ディレクトリの配下に設置しておく
・これらの実ファイルは、人間が管理しやすいように、適宜ファイルやフォルダを分けて作成しておく
↓
●設定
マニフェストファイル( assets/config/manifest.js )に、コンパイルと最小化の方法を記述しておく
↓
●コンパイルと最小化
すると、ブラウザでアクセスした時、「Sprockets」というGemによって、ブラウザが解釈可能なJS/CSSで、かつ最小化された状態で配信される。
このとき、最小化前のリソースの形式(coffee、Sass、ERB など)に応じて、プリプロセッサエンジン
が動作して変換が実行される(ブラウザが解釈可能な状態に コンパイル
される)。
プリプロセッサエンジン は、拡張子を右から左へ辿って順次実行される。
たとえば「mytest.js.coffee.erb」だったら、ERBのプロセッサが動作した後に、CoffeeScriptのプロセッサが動作し、最終的にブラウザが解釈可能なJSが生成される
【1】assets ディレクトリ
JS/CSS/画像 などのフロントエンドのリソース群は、assets というディレクトリに設置してまとめて管理する。
assets ディレクトリは、アプリケーションの階層に応じていくつか存在する。
app/assets : このアプリケーション固有のフロントエンドリソース
lib/assets : 自作だが、他の案件でも使いまわすような汎用的なフロントエンドリソース
vendor/〜/assets : Gemなどの外部ライブラリがそれぞれ保持しているフロントエンドリソース
また、app/assets ディレクトリの中身は、デフォルトで以下のような構成になっている
$ tree app/assets/
app/assets/
|-- config
| `-- manifest.js # JS/CSS をひとつにまとめる方法を指示するための設定ファイル(後述)
|
|-- images # 画像を設置するフォルダ
|
|-- javascripts # JSを設置するフォルダ
| |-- application.js # 各JSファイルを取りまとめるための「おまとめJS」(後述)
| |-- cable.js # 「Action Cable」関連のスクリプト(連載の別の回で扱いたい)
| |-- channels # 同上
| `-- コントローラ名.coffee # コントローラに付随して生成されるスクリプト
|
`-- stylesheets # CSSを設置するフォルダ
|-- application.css # 各CSSファイルを取りまとめるための「おまとめCSS」(後述)
`-- コントローラ名.scss # コントローラに付随して生成されるCSS
【2】マニフェストファイル
フロントエンドリソースのコンパイルや最小化は、「Sprockets」というGemで実現されている。
マニフェストファイルは、Sprockets にその方法を指示するための設定ファイル。
デフォルトでは以下の通り、assets ディレクトリ配下の「images」「javascripts」「stylesheets」を対象とするように記述されている。
「app/assets/config/manifest.js」
//= link_tree ../images
//= link_directory ../javascripts .js
//= link_directory ../stylesheets .css
【3】試してみる
① これから作るもの
管理者向けページ(admin)と、一般ユーザ向けページ(user)で、JS/CSS を使い分けたい、という状況を想定してみる。
最終的に、以下のような構成となる。
# assetsディレクトリ
app/assets/
|
|-- config
| |-- manifest.js
|
|-- javascripts
| |-- application.js # すべてのJSを取りまとめる、全部入りの「おまとめJS」。デフォルトで存在する。
| |-- sample.coffee # 今回作成するSampleコントローラに付随して生成されるJS
| |
| |-- admin.js # 管理者向けJS群をとりまとめる「おまとめJS」
| |-- admin/ # 管理者向けJS群を設置するためのディレクトリ
| | |-- test.js.coffee.erb # 管理者向けJSのひとつめ。試しにCoffeeScriptとERBを使って書いてみる
| | |-- test2.js # 最小化による結合を確認するため、管理者向けJSをもういっこ設置。こちらは素のJSで書いてみる
| |
| |-- user.js # 一般ユーザ向けJS群をとりまとめる「おまとめJS」
| |-- user/ # 一般ユーザ向けJS群を設置するためのディレクトリ
| |-- test.js.coffee.erb # 一般ユーザ向けJS。こちらもCoffeeScriptとERBを使って書いてみる
|
|-- stylesheets
|-- application.css # すべてのCSSを取りまとめる、全部入りの「おまとめCSS」。デフォルトで存在する。
|-- sample.scss # 今回作成するSampleコントローラに付随して生成されるCSS
|
|-- admin.css # 管理者向けCSS群をとりまとめる「おまとめCSS」
|-- admin/ # 管理者向けCSS群を設置するためのディレクトリ
| |-- test.scss.erb # 管理者向けCSS
|
|-- user.css # 一般ユーザ向けCSS群をとりまとめる「おまとめCSS」
|-- user/ # 一般ユーザ向けCSS群を設置するためのディレクトリ
|-- test.scss.erb # 一般ユーザ向けCSS
# コントローラ
app/controllers/
|-- application_controller.rb
|-- sample_controller.rb # 各ページの表示確認のために今回追加する、サンプルコントローラ
# ビュー
app/views/sample
|-- admin.html.erb # 管理者向けページ
|-- user.html.erb # 一般ユーザ向けページ
② 準備
「assets_test」などの名前で、新規アプリケーションを作成し、コントローラを追加。
あとテスト用のファイル一式を作成しておく。
# アプリ作成
cd /docker-host/share/webapps/
rails _5.1.1_ new assets_test
# Sampleコントローラ作成
cd /docker-host/share/webapps/assets_test
rails generate controller Sample
# テスト用のファイル一式作成(エディタで作成するのが面倒なので...)
cd /docker-host/share/webapps/assets_test/app/assets
mkdir javascripts/admin
mkdir javascripts/user
mkdir stylesheets/admin
mkdir stylesheets/user
touch javascripts/admin/test.js.coffee.erb
touch javascripts/admin/test2.js
touch javascripts/user/test.js.coffee.erb
touch stylesheets/admin/test.scss.erb
touch stylesheets/user/test.scss.erb
touch javascripts/admin.js
touch javascripts/user.js
touch stylesheets/admin.css
touch stylesheets/user.css
# 同様にビューも作成しておく
cd /docker-host/share/webapps/assets_test/app/views/sample
touch admin.html.erb
touch user.html.erb
③ 実装
③-1. JavaScript ファイルを記述
せっかくなので、CoffeScript で、かつERBありで書いてみる。
「app/assets/javascripts/admin/test.js.coffee.erb」
<%
def my_message
"I am an administrator !"
end
%>
window.hello = ->
alert("Hello World! <%= my_message %>")
「app/assets/javascripts/admin/test2.js」
var test2 = 123;
「app/assets/javascripts/user/test.js.coffee.erb」
<%
def my_message
"I am an user !"
end
%>
window.hello = ->
alert("Hello World! <%= my_message %>")
③-2. CSSファイルを記述
せっかくなので、Sass で、かつERBありで書いてみる。
「app/assets/stylesheets/admin/test.scss.erb」
$red:#FF3399;
<% def my_color; "green" end %>
._color_r { color: $red; }
._color_my { color: <%= my_color %>; }
「app/assets/stylesheets/user/test.scss.erb」
$red:#FF3399;
<% def my_color; "blue" end %>
._color_r { color: $red; }
._color_my { color: <%= my_color %>; }
③-3. 独自のおまとめJSを記述する
デフォルトでは、全てのJSを読み込む、全部入りの「おまとめJS」である「app/assets/javascripts/application.js」が存在する。
ここでは別途、先の手順で作成したJSだけを読み込む独自の「おまとめJS」を作成する。
「app/assets/javascripts/admin.js」
//= require rails-ujs
//= require turbolinks
//= require_tree ./admin
「app/assets/javascripts/user.js」
//= require rails-ujs
//= require turbolinks
//= require_tree ./user
↑「require_tree」で、特定フォルダの下のJSだけを読み込ませている
③-4. 独自のおまとめCSSを記述する
デフォルトでは、全てのCSSを読み込む、全部入りの「おまとめCSS」である「app/assets/stylesheets/application.css」が存在する。
ここでは別途、先の手順で作成したCSSだけを読み込む独自の「おまとめCSS」を作成する。
「app/assets/stylesheets/admin.css」
/*
*= require_tree ./admin
*/
「app/assets/stylesheets/user.css」
/*
*= require_tree ./user
*/
↑「require_tree」で、特定フォルダの下のCSSだけを読み込ませている
③-5. 独自の「おまとめJS」と「おまとめCSS」をコンパイル対象に追加する
さきの手順で作成した、特定のJS/CSSだけを取りまとめて読み込む独自の「おまとめJS」「おまとめCSS」を、アセットパイプラインのコンパイル対象に追加する。
「config/initializers/assets.rb」
# これを追加
Rails.application.config.assets.precompile += %w( user.js admin.js user.css admin.css )
③-6. development環境でもコンパイルが実行されるようにする
通常は、開発環境(development)では、結合とか最小化とかされないようにコンパイルはオフにされている(デバッグしづらいので)。
今回は確認のため、あえてdevelopment環境でもコンパイルが実行されるように設定してみる。
「config/environments/development.rb」
# 変更
config.assets.debug = true
↓
config.assets.debug = false
# 追加
config.assets.compile = true
config.assets.js_compressor = :uglifier
config.assets.css_compressor = :sass
③-7. ビュー側で、全部入りJS、全部入りCSS の読み込みをさせないようにする
デフォルトでは、作成したビューすべてに共通レイアウト(app/views/layouts/application.html.erb)が適用されている。
それによって、
・全部入りのJS(app/assets/javascripts/application.js)
・全部入りのCSS(app/assets/stylesheets/application.css)
が読み込まれてしまうので、当該設定を削除する。
「app/views/layouts/application.html.erb」
# 以下を削除
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
③-8. ビューを記述し、個別に JS/CSS が読み込まれるように指定する
それぞれのビューで、ふさわしい JS/CSS(のおまとめファイル)が読み込まれるように指定する。
※もちろん、app/views/layouts/user.html.erb とかを作ってレイアウトファイル側で書き分けても良い。ここではわかりやすく個別のビューに書いてみる。
あと、それぞれに、指定した JS/CSS が適用されているか確認するための簡単なコードも仕込んでおく。
「app/views/sample/admin.html.erb」
<% # admin.js と admin.css を読み込むように指定 %>
<%= stylesheet_link_tag 'admin', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'admin', 'data-turbolinks-track': 'reload' %>
<p>this is admin page.</p>
<div class="_color_r">test1</div>
<div class="_color_my">test2</div>
<div onclick="hello()">click</div>
「app/views/sample/user.html.erb」
<% # user.js と user.css を読み込むように指定 %>
<%= stylesheet_link_tag 'user', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'user', 'data-turbolinks-track': 'reload' %>
<p>this is user page.</p>
<div class="_color_r">test1</div>
<div class="_color_my">test2</div>
<div onclick="hello()">click</div>
③-9. ルーティングの設定を追加する
「config/routes.rb」
# 以下を追加
get 'sample/admin'
get 'sample/user'
④ んで、確認する
④-1. Puma起動
$ cd /docker-host/share/webapps/assets_test
$ rails s
=> Booting Puma
〜 中略 〜
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
④-2. ブラウザでアクセスして表示&動作を確認
http://localhost:3000/sample/admin
http://localhost:3000/sample/user
にアクセスして、
・"test1" と "test2" の文言に、admin/user それぞれのCSSの指定が効いていること
・"click" をクリックした時、admin/user それぞれのJSの文言がポップアップされること
が確認できればOK
④-3. ブラウザでソースを確認
●HTMLのソースを見てみる
http://localhost:3000/sample/admin
のソース
<link rel="stylesheet" media="all" href="/assets/admin-610085c4c4b....css" data-turbolinks-track="reload" />
<script src="/assets/admin-0bb1fe91ae19ddda44c4dd512636646....js" data-turbolinks-track="reload"></script>
- assetsの「admin」側のJSとCSSが読み込まれている
- 末尾のランダム文字列は、ブラウザのキャッシュを制御するために付与されている、MD5のフィンガープリント
http://localhost:3000/sample/user
も同様に、assetsの「user」側のJSとCSSが読み込まれていることが確認できればOK。
●まとめられて最小化されたJSを見てみる
前述の http://localhost:3000/sample/admin
のHTMLソース表示画面で、
"/assets/admin-文字列ほにゃらら.js"
というJSのファイルパスをクリックすると、当該JSファイルの中身が表示される。
このとき、
- 改行とかのないJSがびっしり表示される
- 末尾に、今回作成した2つのJSファイルの内容(hello関数と、var test2 = 123; )が出力されている
上記が確認されれば、JSの結合と最小化が実行されているということなのでOK!
●まとめられたCSSを見てみる
前述の http://localhost:3000/sample/admin
のHTMLソース表示画面で、
"/assets/admin-文字列ほにゃらら.css""
というCSSのファイルパスをクリックすると、当該CSSファイルの中身が表示される。
このとき、
._color_r{color:#FF3399}._color_my{color:green}
のように改行なしでCSSが表示されれば、最小化が実行されているということなのでOK!
アセットパイプラインは割りと複雑なのでちょっと苦戦した。 …c(゚^ ゚ ;)ウーン
連載の別の回(または別記事)で、webpack も試してみたいと思う。
今日はここまで!