アセットパイプラインの仕組みについて

主に「Ruby」や「Rails」を学習している者です。

今回は、「アセットパイプライン」の仕組みについて書いています。

# 環境

Ruby：2.6.4
Rails : 5.2.3


# アセットパイプラインとは？

アセットとは、「JavaScript」「CSS」「画像」などのリソースのことをアセットと言います。

このアセットを効率的に扱うための仕組みのことを「アセットパイプライン」と言います。

アセットパイプラインは「Sprockets」というgemによって提供されています。

「Sprockets」というgemはRailsをインストールした時点で、自動でインストールされます。

「Gemfile.lock」には「Sprockets」が記載されています。

# アセットパイプラインの仕組み

アセットパイプラインは大きく４つの機能を提供しています。

１、高級言語のコンパイル

２、アセットの連結

３、アセットの圧縮

４、ダイジェストの付与

## 高級言語のコンパイル

![20210503100503.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307408/ddae2a4f-e303-38fb-4a8a-2fedacf6bf65.png)

上記の画像を見てください。

`app/assets/stylesheets`配下にCSSのファイルがいくつかあります。

しかし、よく見ると拡張子が`.scss`となっています。

これはSASSというCSSのプリプロセッサーというCSSをもっと楽に記載して、レスポンシブ対応がしやすい書き方ができるものです。

```ruby
# sample.scss

// ==============================
// 変数
// ==============================
$font-color_sub1: #b6a489;

$site-width: 980px;

$font-size_s: 12px;

// ==============================
// mixin
// ==============================
// @mixinは関数のようなものを作れる
@mixin mq($breakpoint: sm) {
  @media #{map-get($breakpoints, $breakpoint)} {
    @content;
  }
}

// ==================================
// footer
// ==================================
.footer {
  background: #555;
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 80px;
  @include mq() {
    font-size: $font-size_s;
  }
}
```

上記のような感じで「CSSで変数」が使えたり、「mixinという関数」みたいなものが使えたり、

CSSを階層構造のように書けるのがSCSSというものです。

で、このSCSSファイルはそのままではブラウザで読み込むことができません。

ブラウザはCSS（拡張子が.css）ファイルは読み込めますが、SCSSファイルは読み込めません。

英語しか理解できない人に、日本語で話しても伝わらないのと同じです。

```ruby
# sample.css

.footer {
  background: #555;
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 80px; }
  @media screen and (max-width: 414px) {
    .footer {
      font-size: 12px; } }
```

ではどうしないといけないかですが、SCSSで書いたファイルをCSSファイルに変える必要があります。

要は「英語」を「日本語」に翻訳すると言うことです。

上記のコードはCSSに変換されたコードです。

この作業を**コンパイル（変換）**と言います。

なので**高級言語のコンパイル**の高級言語は「SCSS」ファイルなどを指します。

それをコンパイル（変換する）ということです。

JavaScriptも同じです。

CoffeeScriptというJSの「AltJS」というものがあります。

[AltJS](https://w.atwiki.jp/sevenlives/pages/2557.html)というのは「代わりになるJS」という意味です。

AltJSには「TypeScript」なども含まれます。

当然CoffeeScriptで書いた場合は拡張子が`.coffee`になります。

しかしブラウザは拡張子が`.coffee`のファイルは読み込めません。

なので、JavaScriptファイル（拡張子が.jsのファイル）に変換しないといけません。

これも高級言語のコンパイルです。

## アセットの連結

![20210503101012.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307408/69624212-fad8-0701-c1a3-e985d436f41c.jpeg)

アセットの連結を理解するにはブラウザとサーバーの通信を知る必要があります。

上記の画像を見てください。

サーバー側にHTMLファイルがあります。

１、ブラウザはサーバーに「HTMLファイルください」とリクエストを投げます。

２、サーバーはリクエストに対してブラウザが要求しているHTMLファイルを返します。

３、ブラウザはサーバーから受け取ったHTMLを上から１行１行読み込みます。

４、すると`<link rel="stylesheet" href="">`みたいなコードがあります。これはCSSファイルを読み込むコードですが、**ブラウザはCSSのファイルを持っていません。**

５、ブラウザはCSSのファイルを持っていないので、サーバーにCSSファイルくださいと「リクエスト」を投げます。

という流れで通信をします。

で、今回は`<link rel="stylesheet" href="">`というコードが３つあるので、３回サーバーにCSSファイルくださいと「リクエスト」を投げます。

JSファイルも同じです。

`<script src="">`というコードが３つありますが、ブラウザはJSのファイルを持っていないので、サーバーにリクエストを投げてファイルをもらいます。

なので３回サーバーと通信します。

CSSファイルで３回、JSファイルで３回もサーバーと通信していたら、負荷がかかります。

じゃあCSSを１個に、JSを１個にまとめてしまえー。という話が「アセットの連結」です。

連結するには１番大元のファイルが必要です。

この１番大元のファイルを「マニフェスト」と言います。

```
app/assets/stylesheets/application.scss
app/assets/javascripts/application.js
```

それがRailsで言うと上記２つです。（変えることもできる）

```ruby
# app/assets/stylesheets/application.scss

@import 'top';
@import 'bootstrap';
@import 'font-awesome-sprockets';
@import 'font-awesome';
```

CSSをこのファイル１つにまとめています。

```ruby
# app/assets/javascripts/application.js

//= require jquery3
//= require popper
//= require bootstrap-sprockets
//= require rails-ujs
//= require activestorage
//= require_tree .
```

JSの１つのファイルにまとめています。

JSファイルでは`//= require`や`//= require_tree`を使います。

・//= require

→ 指定したJSファイルの内容を、記述した位置に取り込みます。（拡張子は不要）

・//= require_tree .

→ 指定したフォルダ配下にある全JSファイルを結合して、記述した位置に取り込みます。

`//= require_tree .`なので、`app/assets/javascripts`配下にあるJSファイルを全て取り込みます。なので、`app/assets/javascripts`配下にあるJSファイルを全て取り込みたくない場合は、ここの記述を`//= require_tree .`の記述を`//= require`で個別に記載してJSファイルを読み込むようにしてあげます。

`jquery3`とか`rails-ujs`とか`app/assets/javascripts/application.js`ファイルの位置から見当たらないよ？と思うかもしれません。

これはアセットの探索パスに基づいて「Sprokest」が勝手に読み込んでくれます。

### アセットの探索パス

アセットの探索パスに基づいて「Sprokest」が勝手に読み込んでくれます。とありますが、どう言う意味でしょうか？

Railではアセットの探索パスはデフォルトで

・app/assts/*　（最優先）

・lib/assets/*

・vender/assets/*

が探索パスに設定されています。

この配下にJSファイルやCSSファイルを置くと、勝手に取り込んでくれます。

### デフォルトのアセットの探索パスに追加をしたい

Railではアセットの探索パスはデフォルトで

・app/assts/*　（最優先）

・lib/assets/*

・vender/assets/*

が探索パスに設定されています。と書きました。

じゃあデフォルトの探索パス以外を探索パスとして設定したい場合はどうするのでしょうか？

例えば`yarn`と言うフロントエンドのパッケージ管理ツールでAdminLTEと言うライブラリをインストールしてみます。

```ruby
yarn add admin-lte@^3.0
```

そうすると`node_modules`というフォルダができて、そのフォルダは以下にAdminLTEを使うために必要はHTMLやCSSやJSファイルが追加されます。

しかし、`node_modules`というフォルダはアセットの探索パスに含まれていません。

その場合は`config/initializars/assets.rb`に設定を書きます。

```ruby
# config/initializars/assets.rb

Rails.application.config.assets.paths << Rails.root.join('node_modules') # マニフェストからの検索パスに追加（デフォルトで記載されている） 
```

上記のように書くと`node_modules`配下もアセットの探索パスに含めることができます。

### 連結の大元のファイル（マニフェストファイル）を変更したい

```
app/assets/stylesheets/application.scss
app/assets/javascripts/application.js
```

今まで`application.scss`や`application.js`が連結の大元になるマニフェストファイルと説明しました。

では、この２つ以外を大元のマニフェストファイルにしたい場合はどうするのでしょうか？

例えば

・app/assets/stylesheets/admin.scss

・app/assets/javascripts/admin.js

上記２つのファイルを大元のマニフェストファイルに設定したいとしましょう。

その場合は`config/initializars/assets.rb`に下記のコードを追加します。

```ruby
# config/initializars/assets.rb

Rails.application.config.assets.precompile += %w[admin.js admin.css] # プリコンパイルの設定
```

一般ユーザー画面では

・app/assets/stylesheets/application.scss
・app/assets/javascripts/application.js

の２つを使う。

管理者画面では

・app/assets/stylesheets/admin.scss

・app/assets/javascripts/admin.js

を使うと言うように分けることができます。

## アセットの圧縮

次はアセットの圧縮です。

```ruby
# sample.css

.footer {
  background: #555;
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 80px; }
  @media screen and (max-width: 414px) {
    .footer {
      font-size: 12px; } }
```

先ほどコンパイルしたCSSファイルで上記のようなコードがありました。

これをよくみてみると、「改行」「空白」が含まれています。

開発している人間にとってはわかりやすいですね。

でもコンピューターからしたら邪魔です。

無駄な改行や空白があると、読み込みが遅くなります。

ではどうするかというと「圧縮」します。

```ruby
# sample.css

.footer{background:#555;color:#fff;display:flex;align-items:center;justify-content:center;height:80px;}@media screen and(max-width:414px){.footer{font-size: 12px;}}
```

上記のように「改行」「空白」を無くします。

これが「アセットの圧縮」と言うものです。

JSファイルも同じようの圧縮します。

## ダイジェストの付与

ブラウザには「キャッシュ」と言う仕組みがあります。

これは、CSSやJSファイルをサーバー側にいちいち取りにいかなくて済みように何度か通信してサーバーからもらったCSSファイルなどをブラウザがわで持っておいて、再利用すると言う仕組みです。

この仕組みはすごくいいんですが、弊害があります。

それは、CSSファイルの中身を変更したのに、ブラウザはキャッシュに保存しているCSSファイルを見に行くことによって、CSSの変更が画面に反映されないと言う問題です。

そのためにRailsにある「ダイジェスト」というのがとても役に立ちます。

Railsのアプリケーションを画面に表示して、ブラウザの開発者ツールを開いてみてください。

```ruby
<link rel="stylesheet" media="all" href="/assets/application.self-705864dc129725964dfcbfb5b9a484f633aad8e3a8ebf81baa5d0046edbabbac.css?body=1">
```

そうすると上記のように`705864dc129725964dfcbfb5b9a484f633aad8e3a8e`みたいな訳わからない文字や数字がひっついていると思います。

これがダイジェストです。

もしコードが変更されたら、このダイジェストも変わるので、ブラウザのキャッシュの影響で修正が反映されない問題を解決することができます。

ちなみに`config/initializars/assets.rb`に下記のようなコードがあります。

```ruby
# config/initializars/assets.rb
Rails.application.config.assets.version = '1.0'
```

この１行がダイジェストの生成に関わっているので、変更することでアセットを期限切れにすることができます。

# ブラウザでアセットを読み込む

「アセットパイプライン」と言う仕組みでアセットが色々と裏側でごにゃごにゃとしていることはわかりました。

では、`app/assets/stylesheets/application.scss`や`app/assets/javascripts/application.js`を読み込むにはどう書けばいいのでしょうか？

```ruby
<%= stylesheet_link_tag    'application', media: 'all' %>
<%= javascript_include_tag 'application' %>
```

そのためには上記のコードを書いてあげればいいです。

JSやCSSのマニフェストファイルは`application.scss`や`application.js`なので、そのファイルを読み込むように記載しています。（拡張子は必要ない）

ここで読み込むのはアセットパイプラインによって連結されたファイルです。

ちなみに「連結の大元のファイル（マニフェストファイル）を変更したい」で記載しましたが、

マニフェストファイルに

・app/assets/stylesheets/admin.scss

・app/assets/javascripts/admin.js

を追加した場合は

```ruby
<%= stylesheet_link_tag    'admin', media: 'all' %>
<%= javascript_include_tag 'admin' %>
```

上記のように`admin`に変えてあげると、`admin.scss`や`admin.js`を読み込んでくれます。

「連結の大元のファイル（マニフェストファイル）を変更したい」で記載した設定をしないで、

```ruby
<%= stylesheet_link_tag    'admin', media: 'all' %>
<%= javascript_include_tag 'admin' %>
```

上記のコードを記載した場合はエラーになります。

エラー内容は下記になります。

> Asset was not declared to be precompiled in production.
Add `Rails.application.config.assets.precompile += %w( admin.css )` to `config/initializers/assets.rb` and restart your server

内容としては「アセットがプロダクションでプリコンパイルされるように宣言されていませんでした。」と言う内容です。

なので、`config/initializers/assets.rb`で``Rails.application.config.assets.precompile += %w( admin.css )``と設定を追加しろ。言われます。

なので設定を追加していきましょう。

その前に`node_modules`フォルダまでのパスがアセットパイプラインのpathとして通っているか確認します。

```ruby
% rails c
irb > Rails.application.config.assets.paths
```

こちらを打つとnode_modulesへのpathが出てくるはずです。

次にプリコンパイルの設定をします。application以外のマニフェストファイルを個別に読み込みたい場合はプリコンパイルの設定をしないとエラーが起きてしまいます。

他のマニフェストや、個別のスタイルシート/JavaScriptファイルをインクルードしたい場合は、config/initializers/assets.rbのprecompileという配列を使用します。（Railsガイド）

```ruby
# config/initializars/assets.rb

# 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[ admin.js admin.css ] # プリコンパイルの設定
```

config配下のファイルは設定ファイルなので、サーバーが起動したタイミングで１度だけ読み込まれます。

なので、何か変更を加えたらサーバーを再起動しないと反映されないので注意が必要です。

```ruby
# app/views/admin/user_sessions/new.html.erb
# app/views/admin/dashboards/index.html.erb

<%= stylesheet_link_tag    'admin', media: 'all' %>  # admin.scssファイル（マニフェストファイル）を読み込む

<%= javascript_include_tag 'admin' %> # admin.jsファイル（マニフェストファイル）を読み込む
```

ビューファイルに作成したマニフェストファイルを読み込むようにしたいのでCSSとJSファイルを読み込む記述を記載します。

これで画面を表示すると見た目が整った画面になっていると思います。

[https://railsguides.jp/asset_pipeline.html#パスの検索](https://railsguides.jp/asset_pipeline.html#%E3%83%91%E3%82%B9%E3%81%AE%E6%A4%9C%E7%B4%A2)

[https://railsguides.jp/asset_pipeline.html#アセットをプリコンパイルする](https://railsguides.jp/asset_pipeline.html#%E3%82%A2%E3%82%BB%E3%83%83%E3%83%88%E3%82%92%E3%83%97%E3%83%AA%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB%E3%81%99%E3%82%8B)