パーフェクトRuby on Rails 4章 メモ・雑感

# フロントエンドの開発手法

## 雑感

この章はRailsにおけるJSやCSSなどフロントエンドの管理や機能についての内容でした。特に今まで雰囲気理解だった`Webpacker`や`Turbolinks`など、APIモードで行った個人開発では触れられなかった技術について基礎から学べたのは勉強になりました。以下、私の環境において発生した不具合の解決方法も交えた忘備録です。

## 4-1 Webpackerを使ってJavaScriptsを管理する

:::note warn
テキスト通りに進めると私の環境(ブラウザ: chrome,ほか以下参照)だと,`destroy`メソッドの実行などがJSが読み込まれずうまく動作しませんでした。開発ツールのコンソールにエラーメッセージが表示されたので追加パッケージをインストールすると解決しました。

```
$ ruby -v
ruby 2.7.8p225 (2023-03-30 revision 1f4d455848) [x86_64-darwin23]
$ bin/rails -v
Rails 6.0.6.1
```

```
$ yarn add @babel/plugin-proposal-private-methods @babel/plugin-proposal-private-property-in-object --dev
$ rm -rf node_modules
$ yarn install
$ bin/webpack
```
:::

### webpack-dev-serverを利用したビルド

- Webpackerでは必要なタイミングで自動的にビルドが実行される。つまり、JSファイル更新後のページへの初回アクセス時に毎回JSビルドが実行されることとなり、待ち時間が発生して開発効率に関わることとなる
- webpack-dev-serverを起動しておくと、webpack管理下のファイルの更新を検知し、すぐにビルドを実行することができる。実際の開発では`rails s`でサーバーを起動するのと並列で起動させておくことでJSをページへアクセスする前にあらかじめビルドされた状態にしておくことができる

```
$ bin/webpack-dev-server
ℹ ｢wds｣: Project is running at http://localhost:3035/
ℹ ｢wds｣: webpack output is served from /packs/
略      
ℹ ｢wdm｣: Compiled successfully.
```

- 開発環境では、JSファイルはwebpack-dev-serverから配信され、本番環境ではコンパイル済みのファイルがRailsから配信される


## 4-2 SprocketsによるCSSの管理

### 本番環境向けのビルド

- 通常のビルド時は開発時の利便性を考慮したファイルの内容が記述されており、縮小なども行われない。そのため、それらの本番環境用では開発時以外では不要な機能を排したよりファイルサイズの少ないビルド方法が用いられる
- 開発時は画面にアクセスするタイミングで動的にビルドされるが、本番環境ではあらかじめビルド済みのCSSファイルを配信してアクセスによる待ち時間を発生させないようにする(プリコンパイル)
- プリコンパイルを行うためのタスクとして`assets:precompile`がある。実行すると`public/assets`以下に成果物が生成される。なお、オプションを指定しなければ開発環境でのビルドとなりファイルの縮小は行われない

```
$ bin/rails assets:precompile
warning: previous definition of Socket was here
yarn install v1.22.22
[1/4] 🔍  Resolving packages...
success Already up-to-date.
略
Everything's up-to-date. Nothing to do
```

- `assets:precompile`実行時は`Webpacker`のビルド処理も実行され、JSのビルド結果も出力される
- `RAILS_ENV`で`production`を明示することでファイルの縮小が行われ、より軽量なファイルが生成される

```
$ bin/rails assets:clobber <-- assetsファイルの削除
略
$ RAILS_ENV=production bin/rails assets:precompile
略
yarn install v1.22.22
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
✨  Done in 4.20s.
Everything's up-to-date. Nothing to do
```

### `assets:precompile`で生成した静的ファイルの読み取り

- `assets:precompile`は開発・本番両方の環境で動作するが、通常生成した静的ファイルを扱うのは本番環境でRailsサーバを起動した場合

```
$ RAILS_ENV=production bin/rails db:setup <-- 本番用DBの作成が必要
$ RAILS_ENV=production bin/rails s 
略
```

- しかし初期状態で本番環境で起動されたページにアクセスしても`public`ディレクトリ配下のJSやCSSは読み込まれない。これはJSやCSSなどのビルド済みの静的ファイルはRails自身ではなく、nginxなどのHTTPサーバやCDNを通じて配信する設計となっているため
- 手軽に確認するためには`config/environments/production.rb`の設定内容に従う。環境変数`RAILS_SERVE_STATIC_FILES`の有無で切り替わり、値はなんでも構わない

```
$ RAILS_SERVE_STATIC_FILES=1 RAILS_ENV=production bin/rails s
```

## 4-3 Railsに組み込まれているJavaScriptの機能

### rails-ujsによる画面の制御

- rails-ujsは画面制御を補助するRails組み込みのJSライブラリ
- 直接JSを書かずにビューテンプレートで少し記述を追加するだけで機能を追加できる

```erb:app/views/users/_form.html.erb
<%= form_with(model: user, local: true) do |form| %>
  略
  <div class="actions">
    <%= form.submit data: {
      confirm: '実行してもよろしいですか？', <-- submit時の確認ダイアログ表示
      disable_with: '送信中'　<-- 二重送信を防ぐ。引数は送信中に表示される
    } %>
  </div>
<% end %>
```

- rails-ujsにはリクエストをAjax化するための機能も含まれている。デフォルトで有効化されており、解除する場合は以下ファイルで`local: true`に設定

```erb:app/views/users/_form.html.erb
<%# デフォルトでAjaxが有効化 %>
<%= form_with(model: user, local: false) do |form| %>
  略
<% end %>
```

:::note info
Ajaxによる通信を行うと表示しているページの操作が可能なまま、非同期でサーバーとのやり取りを行うことができる。必要な部分のみ画面更新を行えて、軽量な画面遷移を実現する。
:::

### Turbolinks

- TurbolinksはAjaxリクエストで取得したデータによって表示ページの`body`タグのみを切り替えて高速なページ遷移を実現するライブラリ
- `body`タグのみを入れ替えるため、`window.foo`のようにグローバルオブジェクトにデータを保存するとページ遷移とともにデータが初期化されない
- フロントエンドを自分たちでカスタマイズしたい場合は初期インストール時に省くことができる

```
$ rails new rails_app --skip-turbolinks
```

- `body`タグのみを入れ替えるので、JSの`onload`や`DOMContentLoaded`といったイベントが発火しない。その代わり、いくつか独自のタイミングで発火するイベントが用意されている

```app/javascript/packs/application.js
略
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")

// 初回レンダリング時と、Turbolinksでbodyが切り替わった時に発火するイベント
document.addEventListener('turbolinks:load', function() {
  // Call your JS functions here
  console.log('Turbolinks loaded')
})
略
```

## 4-4 控えめなJavaScriptフレームワークStimulus

- Turbolinksを使った開発に有用なライブラリとしてフロントエンドに対して規約をもたらすためのフレームワークStimulusがある。`Webpacker`を利用している場合は以下でインストール

```
$ bin/rails webpacker:install:stimulus
```

- 他のJSフレームワークと違い、フロントエンドでHTMLを生成せず、　サーバーから返す。フロントで表現できる幅は狭まるがトータルでの開発の工数が少なくなるメリットがある

- HTMLの`data-controller`、`data-action`、`data-target`を通じてJS側に結びついている

```public/index.html
<!-- DOM上に「hello」があればhello_controllerを読み込む -->
<div data-controller="hello"> 
  <input data-hello-target="name" type="text">

  <!-- 監視イベント->コントローラ#アクション -->
  <!-- この場合は、hello_controllerのgreetメソッドが呼ばれる -->
  <button data-action="click->hello#greet">
    Greet
  </button>
  
  <span data-hello-target="output"></span>
</div>
```

```src/controllers/hello_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  // static変数でtargets配列を定義。ターゲットとして扱う名前を宣言する
  //これによってname.TargetやoutputTargetといったプロパティが扱える
  static targets = [ "name", "output" ]

  greet() {
    this.outputTarget.textContent =
      `Hello, ${this.nameTarget.value}!`
  }
}
```