アセットパイプラインとは、Ruby on RailsにおいてJavaScriptファイル、スタイルシート、画像等(これらを総称してアセットと呼ぶ)を管理する仕組みのこと。
以下のバージョンで確認した。
まずはプロジェクトの作成。
$ rails new myapp --skip-turbolinks
$ cd myapp
$ bundle exec rails g scaffold post title:string
$ bundle exec rails db:migrate
これで$ bundle exec rails s
するとローカルサーバーが立ち上がり、http://localhost:3000/posts
にアクセスできる。
以降、基本的にはこのページで動作確認していく。
プロダクションモード
Railsにはproduction
、development
、test
の、3つの環境が用意されている。
ローカルで開発する際はデフォルトではdevelopment
が使われ、そして本番環境ではproduction
を使うのが一般的。
だがdevelopment
とproduction
ではアセットパイプラインの挙動が異なるため、その挙動を正しく学ぶためにはproduction
もローカルで使う必要がある。
その具体的な方法は後述するが、事前に用意しておくべきものがいくつかある。
まずconfig/master.key
。このファイルがないと、production
環境でサーバーを起動することが出来ない。
rails new
したときに生成されるが、Gitの管理から外すのが一般的なので(デフォルトでもそうなっている)、作業環境によっては存在しないかもしれないので注意が必要。
そしてデーターベースの設定。
先程scaffold
で作成したモデルはdevelopment
環境のデータベースにしか存在しないので、production
環境でもそのデータベースを見るようにしておく。
以下のようにconfig/database.yml
を編集する。
--- a/config/database.yml
+++ b/config/database.yml
@@ -22,4 +22,4 @@ test:
production:
<<: *default
- database: db/production.sqlite3
+ database: db/development.sqlite3
概要
アセットパイプラインでは、以下の処理を順番に行っていく。それぞれの内容については後述する。
- コンパイル
- 統合
- 圧縮
- ダイジェスト付与
- 完成したファイルを
public/assets/
に保存する
development
では2
、3
、5
は行われない。1
と4
のみが自動的に行われる。
production
では予め明示的に1~4
を行って、その結果をpublic/assets/
に保存しておく(この作業を、プリコンパイルという)。そしてそれをHTML側が参照する形になっている。
画像
画像では4
と5
のみを行う。
まず、使用したい画像をapp/assets/images
のなかに置く。
例えばapp/assets/images/welcome.png
。
それを、image_tag
で呼び出す。
--- a/app/views/posts/index.html.erb
+++ b/app/views/posts/index.html.erb
@@ -2,6 +2,8 @@
<h1>Posts</h1>
+<%= image_tag 'welcome.png' %>
+
<table>
<thead>
<tr>
image_tag
を使う時はapp/assets/images
がルートパスになる。
これで$ bundle exec rails s
を実行してhttp://localhost:3000/posts
を見ると、無事画像が表示されている。
これはdevelopment
なので、production
でも確認してみる。
production
環境でローカルサーバーを立ち上げるには、以下のコマンドを実行する。
$ bundle exec rails s -e production
こうするとサーバーが立ち上がるが、エラー画面になってしまっている。
表示されている場合はdevelopment
で表示したときのキャッシュが効いているだけなので、キャッシュをクリアすれば表示されないはず。
これを解決するため、一度サーバーを停止させて以下を行う。
$ bundle exec rails assets:precompile RAILS_ENV=production
すると、yarn.lock
と、public/assets
以下にいくつかのファイルが生成される。
これがプリコンパイルである。
生成されたファイルのなかにはpublic/assets/welcome-xxx.png
もある。
xxx
の部分には乱数のような文字列があると思うが、これがダイジェストである。ブラウザのキャッシュ対策のために付与される。
だがこの状態で再度production
でサーバーを立ち上げてページを確認しても、やはり表示されない。
ブラウザの開発者ツールのコンソールログで確認すると、画像だけでなくJavaScriptファイルやスタイルシートも見つからないというエラーが出ている。
該当するファイルは先程のプリコンパイルでpublic/assets/
に生成されているのだが、それを読み込めていない。
実はproduction
でアセットを読み込むにはもう一つ、やっておくべき設定がある。
それが、RAILS_SERVE_STATIC_FILES
という環境変数である。
この値が存在しないと、アセットを正しく読み込んでくれない。
値さえ存在すれば中身はなんでもよいのだが、今回は1
にしておく。
$ export RAILS_SERVE_STATIC_FILES=1
これで再度サーバーを起動すれば、画像が表示され、コンソールログに出ていたエラーも消えているはず。
なお、RAILS_SERVE_STATIC_FILES
という環境変数を見ているのはデフォルトではproduction
だけなので、development
の動作には影響しない。
JavaScriptファイルとスタイルシートについては、ダイジェスト付与だけでなく、コンパイル、統合、圧縮も行う。
development
ではコンパイルとダイジェスト付与だけなので、まずはそれを見ていく。
アセットパイプラインにおいては、個々のJavaScriptファイルやスタイルシートを読み込むのではなく、マニフェストファイルと呼ばれるものを読み込むのが原則になっている。
もちろん個々に読み込むことも可能であり、それについては後述する。
マニフェストファイルは設定ファイルのようなもので、利用するJavaScriptファイルやスタイルシートをここで指定しておくことで、マニフェストファイルを読み込んだときにまとめて読み込まれる仕組みになっている。
実はマニフェストファイルはデフォルトで既に作成されており、しかも既にapp/views/layouts/application.html.erb
で読み込まれている。
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= javascript_include_tag 'application' %>
JavaScriptファイルはjavascript_include_tag
で、スタイルシートはstylesheet_link_tag
で読み込まれる。
前者はapp/assets/javascripts/
、後者はapp/assets/stylesheets/
がルートパスになるので、上記の場合は以下の2つのファイルをマニフェストファイルとして読み込んでいることになる。
app/assets/javascripts/application.js
app/assets/stylesheets/application.css
2つのファイルにはいろいろと書かれているが、特に重要と思われるのは、どちらにも登場するrequire_tree .
という記述。
require_tree
は、指定されたディレクトリ以下の全てのファイルを読み込む。
今回はカレントディレクトリを指しているので、それ以下の全てのファイルを読み込む。
例として、app/assets/javascripts/sample.js
とapp/assets/stylesheets/sample.css
を作成して確認してみる。
diff --git a/app/assets/javascripts/sample.js b/app/assets/javascripts/sample.js
new file mode 100644
index 0000000..8d46f25
--- /dev/null
+++ b/app/assets/javascripts/sample.js
@@ -0,0 +1 @@
+console.log('This is sample.js');
diff --git a/app/assets/stylesheets/sample.css b/app/assets/stylesheets/sample.css
new file mode 100644
index 0000000..ccf22a0
--- /dev/null
+++ b/app/assets/stylesheets/sample.css
@@ -0,0 +1,3 @@
+h1 {
+ color: blue;
+}
この状態でdevelopment
でサーバーを起動してページを見てみると、h1
が青くなっており、コンソールログにはThis is sample.js
と表示されているはず。
また、ページの<head>
の中身を見てみると、以下のようになっており、マニフェストファイルで指定したファイルが全てダイジェスト付きになって読み込まれている。
<link rel="stylesheet" media="all" href="/assets/posts.self-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css?body=1" />
<link rel="stylesheet" media="all" href="/assets/sample.self-d9b74f2b3926f9fbae3ce3ea3396c2c9ef70eafed818d184eab580a54595ce02.css?body=1" />
<link rel="stylesheet" media="all" href="/assets/scaffolds.self-8d6bd2e12bf8382f31c1c80fa0db365cd6866611c10f592870958f179690ed59.css?body=1" />
<link rel="stylesheet" media="all" href="/assets/application.self-f0d704deea029cf000697e2c0181ec173a1b474645466ed843eb5ee7bb215794.css?body=1" />
<script src="/assets/rails-ujs.self-3b600681e552d8090230990c0a2e8537aff48159bea540d275a620d272ba33a0.js?body=1"></script>
<script src="/assets/activestorage.self-0525629bb5bac7ed5f2bfc58a9679d75705e426dafd6957ae9879db97c8e9cbe.js?body=1"></script>
<script src="/assets/action_cable.self-69fddfcddf4fdef9828648f9330d6ce108b93b82b0b8d3affffc59a114853451.js?body=1"></script>
<script src="/assets/cable.self-8484513823f404ed0c0f039f75243bfdede7af7919dda65f2e66391252443ce9.js?body=1"></script>
<script src="/assets/posts.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js?body=1"></script>
<script src="/assets/sample.self-ebf98f6f89562fbfb67cf4e34fbb1605c9aa1492e9c9d8cdc11495da3a1ad7fa.js?body=1"></script>
<script src="/assets/application.self-eba3cb53a585a0960ade5a8cb94253892706bb20e3f12097a13463b1f12a4528.js?body=1"></script>
マニフェストファイルの具体的な書き方については以下を参照。
railsguides.jp
ダイジェスト付与以外にdevelopment
とproduction
で共通して行われる処理として、コンパイルがある。
これは、CoffeeScriptなどを、ブラウザが解釈できる形に変換してくれる機能のことである。
app/assets/javascripts/cs.coffee
を作成して確認してみる。
diff --git a/app/assets/javascripts/cs.coffee b/app/assets/javascripts/cs.coffee
new file mode 100644
index 0000000..3f32e7a
--- /dev/null
+++ b/app/assets/javascripts/cs.coffee
@@ -0,0 +1,2 @@
+name = "Coffee Script"
+console.log "My name is #{name}!"
コンソールログを見るとMy name is Coffee Script!
と表示されている。
ページが読み込んでいるsample.self-xxx.js
の中身を見ると、以下のようにJavaScriptの記法になっている。
console.log('This is sample.js');
しかし、ES2015+のトランスパイルは行われないので、注意が必要。
以下のようなコードは、トランスパイルされることなくそのまま出力される。
diff --git a/app/assets/javascripts/sample.js b/app/assets/javascripts/sample.js
index 1980b45..2171aa1 100644
--- a/app/assets/javascripts/sample.js
+++ b/app/assets/javascripts/sample.js
@@ -1 +1,2 @@
-console.log('This is sample.js');
+const hoge = BigInt;
+console.log(hoge);
そのため、BigInt
をサポートしている最新版のChromeでは動くが、サポートしていないSafariの12.0.1
では以下のエラーになる。
ReferenceError: Can't find variable: BigInt
続いて、production
環境での挙動を見てみる。
そのためにまず、プリコンパイルを行う。
$ rails assets:precompile RAILS_ENV=production
すると、エラーが出るはず。
rails aborted!
Uglifier::Error: Unexpected token: keyword (const). To use ES6 syntax, harmony mode must be enabled with Uglifier.new(:harmony => true).
これは、JavaScriptの圧縮を行っているライブラリがES2015+に対応していないのが原因。
config/environments/production.rb
を編集して設定を変える必要がある。
diff --git a/config/environments/production.rb b/config/environments/production.rb
index a52e199..ab7d911 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -23,7 +23,7 @@ Rails.application.configure do
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
# Compress JavaScripts and CSS.
- config.assets.js_compressor = :uglifier
+ config.assets.js_compressor = Uglifier.new(harmony: true)
# config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed.
その上で再度プリコンパイルすると、上手くいくはず。
production
で起動してページを見てみると、sample.js
、cs.coffee
、sample.css
の内容が正しく反映されている。
しかし<head>
を見ると、読み込まれているのはapplication-xxx
だけになっている。
これは、マニフェストファイルで指定したファイルをひとつにまとめているためである。これが、「概要」で述べた「統合」である。
<link rel="stylesheet" media="all" href="/assets/application-79a17794ee143f211af72e257c0af173f9903be5aab393f962afb547892b4671.css" />
<script src="/assets/application-4a7737dfce7f40904dec8d86d4ffb0662fab4f59c9bc0a9f975e6eee1a4381f4.js"></script>
先程ES2015+の対応を行ったが、それはあくまで圧縮できるようになったというだけで、コードの変換が行われているわけではない。
そのためSafari12.0.1
でページを見るとやはりエラーになる。
BigInt
はあまり使わないと思うが、const
やlet
などもそのまま出力されるため、IE対応が必要な場合などは注意が必要。
マニフェストファイルはapp/assets/javascripts/application.js
やapp/assets/stylesheets/application.css
である必要はない。
例えば、application.js
の階層を変えてみる。
renamed: app/assets/javascripts/application.js -> app/assets/javascripts/base/application.js
erb
側でパスを変えれば、何の問題もなく読み込める。
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 3a1b560..ebd1eb6 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -6,7 +6,7 @@
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all' %>
- <%= javascript_include_tag 'application' %>
+ <%= javascript_include_tag 'base/application' %>
</head>
ただ、require_tree .
はマニフェストファイルのカレントディレクトリ以下を読み込むので、その対象外であるapp/assets/javascripts/cs.coffee
やapp/assets/javascripts/sample.js
は読み込まれない。
この挙動は、development
でもproduction
でも同じ。
個別に読み込む
特定のページだけで読み込みたいファイルなどは、そのファイルを直接指定することも出来る。
例として、http://localhost:3000/posts/new
でのみ、app/assets/javascripts/sample.js
を読み込ませてみる。
javascript_include_tag
で、読み込めばいい。
diff --git a/app/views/posts/new.html.erb b/app/views/posts/new.html.erb
index fb1e2a1..d692c12 100644
--- a/app/views/posts/new.html.erb
+++ b/app/views/posts/new.html.erb
@@ -3,3 +3,5 @@
<%= render 'form', post: @post %>
<%= link_to 'Back', posts_path %>
+
+<%= javascript_include_tag 'sample.js' =%>
まずはdevelopment
で確認。
http://localhost:3000/posts/new
を開くと、エラーページが表示されてしまう。
これを解消するには、config/initializers/assets.rb
にRails.application.config.assets.precompile += %w( *.js )
を追記しなければならない。
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb
index 4b828e8..d6464dc 100644
--- a/config/initializers/assets.rb
+++ b/config/initializers/assets.rb
@@ -12,3 +12,4 @@ Rails.application.config.assets.paths << Rails.root.join('node_modules')
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
# Rails.application.config.assets.precompile += %w( admin.js admin.css )
+Rails.application.config.assets.precompile += %w( *.js )
これでサーバーを立ち上げ直すと、上手くいく。
app/assets/javascripts/sample.js
はhttp://localhost:3000/posts/new
でのみ読み込まれ、他のページでは読み込まれない。
プリコンパイルすれば、production
でも問題なく動く。