content_forを使ってView毎に適用するCSSを切り分ける【Rails・CSS設計】

#目的
デフォルトの設定のままだと、application.cssとかapplication.scssとかでプロジェクトの全ページが全てのCSSを読み込むというような状態になっています。
しかし、それだと複数人にデザインも兼ねた実装をお願いしている時に、命名とかが重なり競合してしまう可能性が高いと考えられます。もちろん命名規則とかコーディングルールとかがあれば、CSSとかの競合はある程度抑えられると思われますが、View毎に読み込むCSSを切り分けた方がより、競合するリスクを抑えれるのではないかということで自分へのメモという意味合いも兼ねて記事にしました。

こうすることで、例えば管理画面にはBootstrapを使用したいがクライアント画面のCSSと競合する可能性があるという場合でも、切り分けにより競合を防ぐ事ができるようになります。

##前提
以下の記述がなされていると仮定して話を進めていきます。

```routes.rb
Rails.application.routes.draw do
  # Top page
  root 'top#index'
end
```

```top_controller.rb
class TopController < ApplicationController
  def index
    ...
  end
end
```

```top/index.html.erb
...
```

```layouts/application.html.erb
<!DOCTYPE html>
<html lang="ja">
  <body>
    <main role="main">
      <%= yield %>
    </main>
  </body>
</html>
```

#手順
##CSSファイルの取り扱い
例えば、必然的に全てのページに必要なcssとかは、そのままで良いため```applicaion.css```は以下のような記述にして必要なcssを読み込めば大丈夫です。以下では例として```reset.css```と```style.css```も読み込んでいます。

```applicaion.css
/*
 *= require_self
 *= require reset
 *= require style
 */

```

##CSSファイルにする必要がないものは全てSCSSファイルに書き換える
特別な理由がないのであれば全てSCSSファイルにしてしまうのが良いです。そうすることで、View毎に必要なpatialファイルを読み込ませることが可能になり、設計が楽になります。
patialファイルというのは、```_○○○○.scss```のような、アンダーバー「_」から始まるファイルのことです。例えば記事の型について書いたものや、フォームで使い回せる記述を書いたものは```_article.scss```とか```_form.scss```とかにしてpartialファイルにしておくことで、プリコンパイルが行われるSCSSファイルからpatialファイルを読み込むことができます。下記参考。

```top.scss
@import "article";
@import "form";

...
```
上記のように、importを書く際は_や拡張子は省略することができます。

##content_forを使う準備をする
自分の場合はmetaタグや、cssの読み込みを```_meta.erb```というファイルに記述しているため一例としてその中身を例に説明します。

```content_for``` はよくmetaタグの内容をView毎に変えるために使われていますが、それを利用してView毎に読み込むCSSファイルを変更していきます。これを利用してView毎に読み込むJSファイルを変更することもできます。

とりあえず、headタグが書かれているファイルに```<%= yield :css %>```を書き足すことができれば大丈夫です。

```_meta.erb
<head>
  <meta charset="UTF-8">
  ...
  <%= csrf_meta_tags %>
  <%= csp_meta_tag %>

  <%= stylesheet_link_tag    'application', media: 'all' %>
  <%= yield :css %> <!-- ここを追記 -->

  <%= javascript_include_tag 'application' %>
 
</head>
```

##viewファイルにcontent_forを記述して読み込みたいファイルを指定する

まずはstylesheets以下にtop.scssというファイルを作成してください。中身は何も書かなくても大丈夫です。

```top.scss
...
```

次に、views/top/index.html.erbに以下を書き足します。

```index.html.erb
<% content_for :css do %>
  <%= stylesheet_link_tag 'top' %>
<% end %>

...
```

##プリコンパイル対象のファイルを追加する

今のままでtop/index.html.erbを見ようとすると以下のようなエラーが表示されるはずです。

<img width="938" alt="スクリーンショット 2019-10-13 0.06.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155553/58882977-b00d-2208-b86a-d99d99dd59c3.png">

今回、```application.scss``` ファイルを用意していないため、今の設定だとscssファイルはprecompileの対象外としてみなされています。そのためエラー文の通り個別に対象となるファイルの名前を追記してあげる必要があります。
追記場所は、```config/initializers/assets.rb```になります。

```assets.rb
# Be sure to restart your server when you modify this file.

# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0'

# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
# Add Yarn node_modules folder to the asset load path.
Rails.application.config.assets.paths << Rails.root.join('node_modules')

# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
Rails.application.config.assets.precompile += %w(
  top.css
 )
# この「top.css」を追記します。

```

上記では、```top.scss```と書くのではなくて、```top.css```と記述します。
以上で作業は終わりとなります。これでトップページもしくは該当ページを参照すると、Viewが表示されているかと思います。

このようにView毎に```content_for```で読み込むSCSSファイルを変えることで、独立したCSSを読み込むことが可能になります。また、使いまわせそうなcssはパーシャルとして```_○○○○.scss```としておくことで、使いまわしたいSCSSファイルから簡単に読み込むことも可能になるので非常に作業がしやすくなると思います。

この例だけだと、本当に切り分けができているのか分かりづらいですが、複数ファイルを作成して試してみると、しっかりView毎に違うSCSSファイルが読み込まれていることが確認できますのでお試しください。

また、このやり方でView毎に読み込むJSファイルを切り分けることも可能ですのでぜひお試しください。


# おまけ
##複数のSCSSファイルを読み込む時の例

例えば、```top.scss```と```common.scss```という2つのファイルを読み込みたい時は

```index.html.erb
<% content_for :css do %>
  <%= stylesheet_link_tag 'top' %>
  <%= stylesheet_link_tag 'common' %>
<% end %>
...
```
このように、stylesheet_link_tagを複数書けば、複数のSCSSファイルを読み込むことが可能です。

パーシャル化したファイルを読み込む場合は前述の通り、下記のように記述することになります。

```top.scss
@import "article";
@import "form";

...
```

##アセットパイプラインとかプリコンパイルって何？という方
より詳しく勉強したい方は、ぜひ公式ガイドを読んでみてください！
https://railsguides.jp/asset_pipeline.html

##追記で少々補足（2021年1月17日）

`config/initializers/assets.rb`にいちいち毎回ファイルを足して書いていくのって面倒ですよねｗ
そういう場合は以下のように書くことで、全てのCSSファイルやJSファイルをプリコンパイルの対象にするよーとすることができます！

```assets.rb
# Be sure to restart your server when you modify this file.

# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0'

# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
# Add Yarn node_modules folder to the asset load path.
Rails.application.config.assets.paths << Rails.root.join('node_modules')

# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
Rails.application.config.assets.precompile += [lambda do |filename, path|
  path.start_with?(Rails.root.join('app', 'assets').to_s) &&
      %w(.js .css).include?(File.extname(filename)) &&
      !File.basename(filename).start_with?('_')
end]

```

なんとなくこれ書いてあるコード読めば意味分かると思いますが、ここはあえて解説しないので分からなければ自身で調べてみてくださいｗ


#おわりに
言葉足らずの部分もあるかと思いますが、また随時分かりやすいように更新していきますm(_ _)m