RailsのHotwireについての知見を増やす

に公開

Rails の Hotwire を触ったログを記録として残すための記事です。

その前に importmap-railsStimulusTurboについて軽くまとめます。

importmap-railsとは

Raiils のバージョン 7 からは、ES6 の js を扱うための webpack や rollup を導入する必要がなくなりました。

本来 jsbundling-rails を使い、好みのバンドラーを選択し、それを使い JS のビルドを実行することが多いはずです。

これまではそれが一般的だったのですが、作りはじめはいいものの、管理するパッケージが増えたり、Node.js のバージョンが上がってくると依存関係で悩まされることが多くなってきます。

Import maps は JavaScript (ES6) の import 文や import() 式で取得するモジュール(ESModules)の URL を制御できる Web 標準です。

その中でも Rails 用に準備されているのがimportmap-railsです。

例えば、import maps を使うと以下のようにしてマッピングします。

<script type="importmap">
{
  "imports": {
    "react": "https://ga.jspm.io/npm:react@17.0.2/index.js",
  }
}
</script>

"react" が論理名で、URL の方が JavaScript モジュールの実体です。

このようにマッピングすれば、他のファイルからこの JavaScript モジュールを論理名を使ってインポートできます。

import React from "react"

Rails で用意した importmap-rails をつかうとマッピング処理自体も自動化してます。

例えば、config/importmap.rbに以下のように記載します。

pin "react", to: "https://ga.jspm.io/npm:react@17.0.2/index.js"

そして、application.html.erb上の<head>タグ内に、<%= javascript_importmap_tags %>を書くだけです。

<!DOCTYPE html>
<html>
  <head>
    ...
    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_importmap_tags %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

これだけで、Rails 上の JavaScript ファイルで、react をインポートできます。

この importmap-rails は初期インストール状態でプロジェクトが作られます。

importmap-railsを使うことのメリット・デメリット

先ほどお伝えした通り、JS のビルドが不要となるので、bundler を使うことが不要となります。

bundler 自体、設定が難しい面もあるため、その辺のセッティングを省くことが可能となります。

複数の JavaScript ファイルを 1 つの大きな JavaScript にまとめるということは、どういうことでしょうか。

それは、複数のファイルのうち 1 つでも変更があれば、できあがる大きな JavaScript 全体が変更されるということです。

しかしビルドが不要となれば、変更は変更のあったファイルだけということになるので、変更のないファイルはキャッシュを利用できるため、新たにダウンロードするファイルは最小限で済みます。

上記のようなメリットを享受できるのが importmap-rails ですが、デメリットもあります。

プリコンパイルしないので、JSX を処理できず、たとえば React コンポーネントはクラスでしか書けません。

bin/importmap pin react react-dom のようにしてパッケージの追加はできます。
しかし、 bin/importmap update react react-dom のように書くことでアップデートはできません。

Stimulusとは

Stimulus の主な仕事は、DOM 操作です。

Stimulus の実態としては、JavaScript のライブラリです。

ですので、Rails 以外でも使うことはもちろん可能ですが、Rails で使う場合は“stimulus-rails”という gem があります。

これは Stimulus を使いやすくするための gem で、更に実装を助けてくれます。

React や Vue と同じようなことができるのですが、これらのライブラリと比較した際のメリットとして以下が挙げられます。

  • 記述量が少ない
  • 環境構築が不要
import { Controller } from "@hotwired/stimulus"
import flatpickr from "flatpickr";

export default class extends Controller {
  connect() {
    var config = { dateFormat: "Y-m-d" }
    flatpickr(this.element, config);
  }
}
f.text_field :select, class: "form-control", data: {controller: "flatpickr"}

Stimulus を使うと、簡単にセレクトボックスに flatpickr を適用可能です。

日本語化する場合は ja ファイルも import する必要があります。

上記では、data-controller=”flatpickr”と指定しています。
これで app/JavaScript/controllers にある flatpickr_controller.js が呼び出されます。

こちらも importmap-rails 同様、初期インストール状態でプロジェクトが作られます。

Turboとは

Turbo は、html の body タグのみ置換する SPA 風のアプリケーションを作成できる Hotwire のコンポーネントの1つです。

Turbo は4つの要素があります。

  • Turbo Drive
  • Turbo Frames
  • Turbo Streams
  • Turbo Native

主にどの単位もしくはどこを Turbo で利用するかによって使い分けされるようになります。

Turbo Drive

Turbo Drive は、ページレベルのナビゲーションに利用されます。

たとえば、リンククリックやフォーム送信のイベントをインターセプトし、デフォルトの操作を上書きして fetch による非同期リクエストで処理します。

そのレスポンスは HTML になっているため、その中の<body>要素だけ抜き出して現在の<body>要素と差し替えるようなことをします。

※正確には<body>の置換に加えて、<head>の一部がマージされます。

上記の通り、<head>要素は変更されないため、画面遷移やイベント処理が高速となります。

本来、CSS や JavaScript のファイルは<head>要素に記載され、読み込まれることでダウンロード(読み込み)処理が実施されます。

<head>要素が変更されないということは、初回でしか実施しなくなるということです。

これらの動きは、SPA のように一部の描画を切り替えるような動きに近いため、SPA のような動きに見えるのです。

React や Vue のように仮想 DOM のような賢い差分更新はしませんが、毎回 assets 類をすべて読み込み直すことがなくなるのでページ遷移が早くなります。

Turbo Frames

簡単に言ってしまえば、Turbo Driveの部分置換バージョンです。

Drive の場合は、<body>全体を差し替えるようなことを実施していました。

一方、Turbo Frames を使うことで<body>要素の一部分だけを fetch による非同期リクエスト処理を実施します。

ただし、Turbo Frames を利用してページ全体を置換する Turbo Drive のような処理もすることは可能となります。

例:ヘッダーとナビゲーションが存在する検索結果ページにおいて、ページネーションを選択することでページ全体を置換せず、検索結果部分だけを置換します。

Turbo Streams

こちらも簡単に言ってしまえば、Turbo Framesの複数部分置換バージョンです。

本来 Turbo Frames を使う場合、1リクエストに対し、Frames でラップした要素の一部分だけを変更するということはできるのですが、それ以外の場所を置換できません。

Turbo Frames でそれを実現するとなると<body>要素を囲うということをしなければなりません。

そのため複数箇所を同時に更新したい場合には Turbo Streams を使うこととなります。

また、Turbo Frames が HTML 要素の置換(get)しかできないのに対して、Turbo Streams では HTML 要素の追加・更新・削除(crud)が可能です。

Turbo Native

IOS や Android のスマホアプリの構築で利用できるものです。

※すみません、スマホアプリに疎くこちらの解説は省略させていただきます..。

Hotwireについて

いよいよここから Hotwire についての説明です。

Hotwire とは、Rails 7 から Rails のデフォルトになった、モダンな Web アプリケーションを作るための新しいアプローチです。

詳しく説明すると、サーバーサイドで生成された HTML を直接ブラウザに送信し、JavaScript を最小限に抑えます。
それに加えて、より高速でインタラクティブなウェブアプリケーションを構築できる Ruby on Rails のフレームワークです。

SPA ではないが、React 等の js ライブラリを使用せずに、SPA 風の UX を実現できる Rails のフレーワークというイメージが近いです。

つまり、これまで紹介してきた Turbo や Stimulus などを組み合わせて Rails 用に仕上げたものというイメージで良いです。

Hotwireを選択するべきケース

SPA と対比し、どちらを使うべきなのかについてですが、あまりデザイン性が求められないスモールな開発に向いてます。

SPA はどうしても開発コストが大きくかかってしまいます。

しかしその分、リッチな画面挙動を実装が可能で、長期的な UI/UX 観点からは SPA の方が良いと思われます。

まとめ

弊社のように社内の基幹システム開発において、リッチなデザインは求められず、機能性重視な面が大きいです。

その状況を踏まえ、React + Rails の構成から Rails の Hotwire 利用のスタックに変更しました。

次回はその Rails に Hotwire を導入するための方法を紹介できたらします。

GitHubで編集を提案

Discussion