アプリケーションの規模が大きくなるに連れてapplication.scssファイルの記述が長くなってきたので、今回コントローラーごとにファイルを分けることにしました。
ちょっと苦戦したり、勉強になったことが多かったので、忘れないために記事にすることにしました。
環境
Ruby 2.5.7
Rails 5.2.4
答え
先に答えだけ書いておきます。
// require_tree .
// require_self
<head>
...
<%= stylesheet_link_tag 'application' %>
...
</head>
この記述と各コントローラー名の.scssファイルをapp/assets/stylesheet/の中に作れば大丈夫です!
ここから少し中身を解説していきます。
経緯
//require_tree . って必要?
最初は、application.scssを共通のスタイルファイルとし、各ページのコントローラー名.scssの2ファイルで全てのページに対応しようとしました。
その時の記述は下記のようになります。
// require_self
<head>
...
<%# application.scssの読み込み %>
<%= stylesheet_link_tag 'application' %>
<%# コントローラー名.scssの読み込み %>
<%= stylesheet_link_tag params[:controller] %>
...
</head>
application.scssファイルの// require_tree .
はapp/assets/stylesheet内の全ての.scssファイルを読み込む記述なので、一旦削除します。
その代わりに、application.html.erbのstylesheet_link_tagでparams[:controller]と書くと、ディレクトリを含めたコントローラーのパスを取得できるので、コントローラーをディレクトリ分けしている場合もこの書き方で対応できます。
しかし、実はこのやり方は.cssファイルならこれで大丈夫なのですが、.scssではエラーになってしまいます。
プリコンパイルで失敗する
.scssは.css形式にコンパイルすることで初めてブラウザに対応できます。
つまり、通常は.scss形式のままでは表示ができないということです。
そして、プリコンパイルされた.scssファイル(.cssファイル)は本番環境ではapp/public/assets下に格納され、ファイル名はハッシュ形式に変更されてしまいます。
つまり、application.html.erbに記述した<%= stylesheet_link_tag params[:controller] %>
ではcssファイルが拾えなくなるということになります。
ハッシュ形式に変更されたファイル名でも、そのハッシュをそのままstylesheet_link_tag
の中で指定したら動作はしますが、このハッシュはファイルが更新されるごとに変更されてしまうので現実的ではありません。
私は当初やりたかった.scssファイルを真にコントローラーごとに分割するというやり方にたどり着くことはできなかったので、冒頭の記述に切り替えました。
// require_tree . の有無の違いについて
application.scssに// require_tree .
がある場合はassets/stylesheet下にある全ての.scssファイルが読み込まれるということになります。
これはあまりあってはならない事なのですが、仮にcssセレクタのクラス名が被った状態でスタイル指定をすると、後に記述している方が優先されてしまうため、思い通り変更ができないなどの、思わぬところで依存関係ができてしまう恐れがあります。
.css(.scss)ファイル読み込みの順番
// require_tree .
// require_self
冒頭のように記述した場合は、全ての.scssファイルが読み込まれた後にapplication.scssが読み込まれます。
また// require_tree .
の中身の順番については辞書順となっており、ファイル名a→zの順番で読み込まれます。
必然的にtree .の中ではファイル名のイニシャルがzに近いほど後から読み込まれるため、優先度が高くなる傾向にあります。
変数用に用意した.scssファイルの扱い
その前に.scssの変数について少しだけ確認しておきます。
scssではプロパティや値を変数にして使い回すことができます。
私の場合は今のところ
*ハンバーガーメニューのtransition
*サイト全体のカラーリング3パターンほど
*メディアクエリのwidth
をそれぞれ変数化して一つのファイルにまとめ、各ファイルでそのファイルを呼び出す記述をしています。
参考までに記載しておきます。
// ハンバーガーアニメーション
$hamburger-transition: 0.3s;
// テーマカラー
$thema-color1: #fff9f9;
$thema-color2: #ffefef;
$thema-color-font: #555;
// メディアクエリ
$media-sp-max: 450px;
$media-pc-min: 1024px;
$media-tb-min: $media-sp-max + 1px;
$media-tb-max: $media-pc-min - 1px;
//例
セレクタ名 {
background: $thema-color1;
}
これでテーマカラーの変更や、メディアクエリのwidth、ハンバーガーメニューのtransitionなどを各ファイル一括で変更できるようにしています。
変数の定義は$から変数名を書き始め、その後に値を入れることで、定義できます。
呼び出すときはその変数名を値のところにそのまま書くだけです。
しかし、このままでは各ファイルで変数が定義されていないので、この変数のみを記載したファイルを各コントローラー名の.scssファイルにインポートする必要があります。
@import "variables";
...
@import "ファイル名"
を記述することで、今回の場合だと、_variablesに書かれた変数が使用できるようになります。
ここで疑問が生まれました。
「application.scssで// require_tree .
を記載している。各.scssファイルには変数ファイルのインポートを記載していているので、コンパイルの時に各ファイルが読み込まれるたびに変数ファイルも都度都度.cssファイルとして読み込まれるのでは?」と。
読み込まれたとしても問題ないと言えば無いのですが、やっぱり無駄が多いと思ったので、さらに調べました。
結果から言うと、知らず知らずのうちにそれを回避していました。笑
共通ファイルをコンパイルから除外する"partial"
どこかの記事をみて変数ファイルの作り方を参考にファイル名を作ったのですが、そのファイル名の先頭に"_"アンダースコアをつけることで、コンパイルはされなくなるようです笑
つまり、今回使っている_variables.scssはアンダースコアから始まるファイル名のため、プリコンパイルからは除外されます。
よく考えてみると確かに変数しか書いていないファイルは直接スタイリングをしないので.cssに変換しても何も意味がないですし、他のファイルを.cssに変換する時に変数部分を中身に置き換えることが出来れば変数ファイルはそれだけ用が済む話だなと、変に納得しました笑
まとめ
ここで冒頭の実装方法に戻りますが、とりあえずはこのやり方で運用してみようかと思っています。
// require_tree .
// require_self
...
$変数名: 値;
...
@import "variables";
...
<head>
...
<%= stylesheet_link_tag 'application' %>
...
</head>
これまではapplication.scssにしかスタイルを書いておらず、コメントを駆使してコントローラー名を書いてブロックを作ったりしていましたが、コントローラーごとにファイルが分けられるだけでもメンテナンスがやりやすくなるかなと思っています。
もし万が一どうしてもクラス名が被ってしまう場合については.scssの特徴でもあるセレクタのネストを用いて視覚的にもわかりやすく記述していきます。(使い回し用のclass名(flexやgrid、btnなど)は別途application.scssファイルに記述しています。)
// require_tree .
を使わずに各コントローラーごとの.scssファイルをプリコンパイルする方法があればご教授いただけると幸いです!
また、質問や解釈の違い、記述方法にも違和感などありましたら、コメント等でご指摘いただけると幸いです。
最後まで読んでいただきありがとうございました!
参考サイト
Railsガイド - アセットパイプライン
Web Design Leaves - SASS
CSS HappyLife - Sassを覚えよう!Vol.7】ファイルを分割して管理を楽に(partialについて)
HACK NOTE - Sass:変数を別ファイルで管理しよう