Rails内でアプリのJSやCSSとは関係ない描画用ページをつくることにしたのだが、アセット周りがよくわかってなかったので勉強したことについてまとめる。
用語
アセット
静的なコンテンツのこと。主にフロント用のファイル(stylesheet, js, font, image等)
プリコンパイル
publicディレクトリにアセットをまとめたり、圧縮したりして設置すること
マニフェスト
どのファイルをプリコンパイルするか明記したもの
Sprockets と Webpacker
Railsにはアセットをパッケージするためのツールが2つある。SprocketsはもともとRailsに実装されていたもので、WebpackerはRails 6.0から標準実装されたWebpackをRailsに組み込むためのgemである。
プリコンパイルの流れ
プリコンパイルまでのフローはだいたい以下の通り
- 参照するディレクトリを設定
- ディレクトリにファイルを設置
- マニフェストを作成
- プリコンパイル
Sprockets
参照ディレクトリ
まずSprocketsについて。Sprocketsには参照するディレクトリが3つある。
- app/assets
- lib/assets
- vender/assets
上から順にアプリ内用、アプリの範囲外用、外部のヴェンダー用のアセット置き場になっている。そのためコントローラーを生成するとapp/assets/stylesheet内にscssファイルが生成される。
ディレクトリのリストはRails.application.config.assets.pathsに格納されている。
マニフェスト
Sprocketsにはマニフェストを決定する方法が2つある。
- app/assets/config/manifest.js
- Rails.application.config.assets.precompile
これらはどちらもトップレベルのマニフェストでこの中に記述されたものはプリコンパイルされる。
//= link_tree ../images
//= link_directory ../stylesheets .css
ここに= link_tree ../imagesと書かれているので、app/assets/images/以下に設置したファイルは特に何か設定しなくてもプリコンパイルされ、asset_pathやimage_tagなどのViewヘルパーメソッドで読み込むことができる。
またlink_directory ../stylesheets .cssと書かれているのでapp/assets/stylesheet内のcss, scssがプリコンパイルされる。ただしサブディレクトリ以下はプリコンパイルされない。
マニフェスト内で記述したファイルはsass-railsが読み込んでくれる。
app/assets
├stylesheets
│└application.scss
└fonts
└hoge/otf/hoge.otf
```
とあった場合にapplicaton.scss内で
```app/assets/stylesheet/application.scss
/*
*= require_self
*/
@font-face {
font-family: "Hoge";
src: asset-url("hoge/otf/hoge.otf") format("opentype");
font-weight: normal;
font-style: normal;
}
```
と記述すればView内でasset_url("hoge/otf/hoge.otf")で呼び出せる。ポイントはRails.application.config.assets.pathsに含まれているパス(この場合\<Rails.root\>/app/assets/fonts)は省略すること。またgemの名前の通りsassかscssでないとこれらのヘルパーは解決されないのでscssに記述すること。
もう一つの方法はconfig/initializers/assets.rb内に
````config/initializers/assets.rb
Rails.application.config.assets.precompile += [
"<filename>"
]
と追記することによって、個別に参照ディレクトリ内のプリコンパイルするファイルを設定できる。ここもRails.application.config.assets.pathsに含まれているパスは省略して書く。
RailsはRails.application.config.assets.pathsに含まれているディレクトリの中にRails.application.config.assets.precompileのパスに該当するファイルがないかを探してプリコンパイルしている。検索順序はRails.application.config.assets.pathsの要素順。
さらにマニフェストをスタイルシートに書くことでCSSをまとめることができる。Rails.application.config.assets.pathsに含まれているものならマニフェストファイルから参照できる。
/*
* ./hoge.cssを取り込む
*= require ./hoge
*
* ./fooディレクトリ配下の全てのcssを取り込む
*= require_directory ./foo
*
* ./barディレクトリ階層下の全てのcssを取り込む
*= require_tree ./bar
*
* 自分自身に記載されたコードを取り込む
*= requrie_self
*/
詳しくは以下を参照。
プリコンパイル
最後にアプリのルートディレクトリで以下を実行すればpublic/assetsの中にアセットファイルが生成される。
$ rails assets:precompile
Webpacker
参照ディレクトリ・マニフェスト
WebpakcerはRails 6.0からJavascriptをプリコンパイルするために実装されている。しかし勿論JS以外もプリコンパイルできる。
Webpacker用の設定ファイルはconfig/webpaker.ymlとconfig/webpack/environment.jsがある。後者はRailsがよしなに設定したwebpack.config.jsに手を加えるためのファイル。
参照先と出力先はconfig/webpaker.ymlに以下のように書かれている。
default: &default
source_path: app/javascript
source_entry_path: packs
public_root_path: public
public_output_path: packs
cache_path: tmp/cache/webpacker
webpack_compile_output: true
上からわかるようにapp/javascript/packs以下が参照ディレクトリで、プリコンパイルしたものはpublic/packs配下に出力されるようにデフォルトで設定されている。
またコンパイルするファイルの拡張子のリストもここで設定されている。
static_assets_extensions:
- .jpg
- .jpeg
- .png
- .gif
- .tiff
- .ico
- .svg
- .eot
- .otf
- .ttf
- .woff
- .woff2
extensions:
- .mjs
- .js
- .vue
- .sass
- .scss
- .css
- .module.sass
- .module.scss
- .module.css
- .png
- .svg
- .gif
- .jpeg
- .jpg
- .json
Nodeモジュール含め、コンパイルしたいファイルの拡張子がここに含まれていないと読み込んでくれない。(Webpacker 6からconfig/webpack/base.jsでresolve extensionsに直接記入するようになった)
マニフェスト的なものは特になく、app/javascript/packs内のJSファイルにapp/javascript配下のファイルを相対パスで渡せばアセットとして利用できる。例えばapp/javascript配下にimagesディレクトリを設置して
app
└javascript
├packs
└images
└hoge.png
app/javascript/packs/application.jsに以下のように記述すれば
const images = require.context('../images/', true)
Viewにて以下のようにアセットを表示できる。
= image_pack_tag "images/hoge.png"
他のヘルパーについては以下を参照。
プリコンパイル
Webpackerは以下のコマンドでコンパイルできる。
$ bin/webpack
また以下のコマンドを実行してサーバーを立てれば、JSに変更があるたびにコンパイルされる(WSLだと重くなるので使ってない)。
$ bin/webpack-dev-server
確認
Railsコンソール内でViewヘルパーを呼び出すか、publicディレクトリの中を確認すればプリコンパイルされているか確認できる。
pry(main)> helper.assert_path("<asset>")
$ find public/ -type f | grep <asset>
使い分け
下の記事にまとまっている。
- Sprocketsでやる場合: ビューはJavaScriptファイルで公開されているものとやりとり可能(変数アクセスや関数呼び出しなど)
- Webpackでやる場合: JavaScript packに含まれているものにはビューからアクセスできない