環境が整ったら、Reactアプリを作成していく
土台のアプリケーションを作成する
アプリケーション環境が整ったら、土台となるRailsアプリケーションを作成します。作成するアプリケーションのライブラリ構成は以下の通りです(行頭の[-]はデフォルト構成から外すもの、[+]は加えるもの)。
- [-]Sprockets(sprockets、sprockets-rails)
- [+]Propshaft(propshaft)
- [-]importmap-rails
- [+]jsbundling-rails(esbuild)
- [-]Hotwire(turbo-rails、stimulus-rails)
今回も、アセットパイプラインにSprocketsに替えてRails 7から使用できるようになったPropshaftを使います。esbuildを使うので、Sprocketsの持つビルド機能は不要なため、Propshaftの軽量さを生かすことにします。
アプリケーションは、rails newコマンドで作成します。アプリケーションの名前はreact_app_2とします。デフォルトのSprocketsに代わりPropshaftを使いますので、-aオプションでpropshaftを指定しています。そしてesbuildを使いますので、-jオプションでesbuildを指定しています。さらにHotwireのインストールをスキップしますので、--skip-hotwireオプションも指定しています。これらのオプションについては第2回で紹介しましたので、不明な場合には参照してください。
% rails new react_app_2 -a propshaft -j esbuild --skip-hotwire …略… Add "scripts": { "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds" } to your package.json …略…
アプリケーション作成時に、上のようなメッセージ(Add ~)が緑色で表示されるはずなので、指示される内容に--loaderオプションを加えて、package.jsonファイルに追記します。
{ "name": "app", "private": "true", "dependencies": { "esbuild": "^0.14.38" }, ←カンマを追記 "scripts": { ←ここから "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --loader:.js=jsx" } ←ここまで }
追記した内容は、アプリケーションの実行に先立って実行されるbuildコマンドです。バンドラーであるesbuildの実行がその内容になっていますが、引数やオプションの意味について、標準では指定されていないものも含めて表にまとめておきます。
引数またはオプション | 概要 |
---|---|
app/javascript/*.* | ビルドするソースを指定する |
--bundle | 依存関係をインライン化する。esbuildはデフォルトでは依存関係をバンドルに含まないので、明示的に有効化する必要がある |
--sourcemap | ソースマップを生成する |
--outdir= <dir> | 出力先を指定する |
--minify | ビルドしたファイルを圧縮する |
--watch | ウォッチモードを有効にする |
--loader:.js=jsx | JSXを.jsファイルに対して有効にする |
今回追加した--loaderオプションは、.jsファイルに対してJSX構文の処理を有効にするための指定です。これにより、Reactコンポーネント中でJSX構文によるHTMLの記述が可能になりますが、実際の内容については後で紹介します。
アプリケーションが作成できたら、続く作業のためにcdコマンドでアプリケーションのフォルダに移動しておきます。
% cd react_app_2
この段階で、フロントエンド関連のファイルがいくつか生成されています。後ほどこれらに手を入れていきますが、どのようなフォルダやファイルがあるかここで見ておくことにしましょう(フロントエンド関連でないフォルダ、ファイルは省略しています)。
react_app_2 ├── app │ ├── assets … アセットファイルを置くフォルダ │ │ ├── builds … ビルドされたJavaScriptアセットが置かれるフォルダ │ │ │ ├── application.js … ビルドされたJavaScriptファイル │ │ │ └── application.js.map … 上記のソースマップ │ │ ├── images … 画像ファイルを置くフォルダ │ │ └── stylesheets … CSSファイルを置くフォルダ │ │ └── application.css… アプリケーション共通のスタイルシート │ └── javascript … JavaScriptファイルを置くフォルダ │ └── application.js … アプリケーション共通のJavaScriptエントリポイント ├── node_modules … Node.jsモジュールが置かれるフォルダ │ ├── esbuild … コンパイルされたesbuild(下位は省略) │ └── esbuild-darwin-64 … macOS用にコンパイルされたesbuild(下位は省略) ├── package.json … yarnパッケージマネージャの管理ファイル ├── public … アプリケーションの公開フォルダ │ └── assets … ビルドされたアセットが置かれるフォルダ └── yarn.lock … yarnパッケージマネージャのロックファイル
app/assets/buildsフォルダには、esbuildがビルドした成果物が置かれます。ここのファイルが、アセットパイプラインにより公開フォルダにコピーされます。esbuildはJavaScriptアセットをビルドしてくれますが、公開フォルダへ設置してはくれませんので、他のアセットの設置とともにアセットパイプラインで担うのです(後述の「バンドラーとアセットパイプライン」を参照)。
また、node_modulesフォルダにはアプリケーション作成後には2つのフォルダがありますが、これらはesbuildのコンパイル済みのバイナリを格納しています。ほかには、yarnパッケージマネージャのためのpackage.jsonファイルとyarn.lockファイルがあります。
コントローラを作成しルーティングを設定する
続けてrails generate controllerコマンドで、コントローラをアクションも指定して作成します。コントローラ名はreact、アクション名はhelloとします。
% rails generate controller react hello …略…
これで、コントローラapp/controllers/react_controller.js、ビューapp/views/react/hello.html.erbが作成されます。ルートページを、作成したreact#helloアクションに変更しておきましょう。
Rails.application.routes.draw do get 'react/hello' # ルートページをreact#helloアクションに root "react#hello" end
ここでPumaサーバを起動するのですが、esbuildを使う場合はアプリケーションの実行前にJavaScriptアセットのビルドが必要になるため、rails serverコマンドではなくbin/devコマンドを使用します。bin/devコマンドでは、foremanというプロセス管理のためのライブラリを使用します(アプリケーション作成時にインストール済み)。foremanの役割を見るために、その設定ファイルであるProcfile.devを見てみましょう。
web: bin/rails server -p 3000 js: yarn build --watch
2つのエントリがありますが、これがプロセス起動の指示です。web行は従来のrails serverコマンドの実行を指示します。js行はyarnパッケージマネージャでbuildコマンド、すなわち、package.jsonに追記したesbuildコマンドの実行を指示します。--watchオプションが指定されていますので、Pumaサーバの起動中にアセットファイルが変更されたとしても、それを検出して次回のリクエスト時にビルドが改めて実行されるようになっています。
Pumaサーバが起動したら、「http://localhost:3000/」にWebブラウザでアクセスして、react#helloが呼び出されるのを確認しておきます。これで土台のアプリケーションの準備ができました。
Reactをインストールする
esbuildを使う場合、Reactモジュールのインストールが必要です。importmap-railsを使う場合にはCDNなどから直接モジュールをインポートするので、インストールは不要でした。インストールは、yarnパッケージマネージャを使います。yarn addコマンドで、reactとreact-domという2つのパッケージをインストールします。
% yarn add react react-dom yarn add v1.22.18 [1/4]
Reactコンポーネントを作成する
Reactモジュールをインストールしましたので、Reactのコンポーネントを作成します。コンポーネントは、esbuildのビルド対象であるapp/javascript以下にcomponentsフォルダを作成して配置します。今回のコンポーネントは、ビュー中の指定するdiv要素に対して、「こんにちは○○さん!」というメッセージを設定するというシンプルなものです。コンポーネントのファイルは、app/javascript/componentsフォルダにreact_hello.jsとして作成します。
import React from 'react' (1) import {createRoot} from 'react-dom/client' const Hello = props => ( (2) <div>こんにちは {props.name} さん!</div> ) Hello.defaultProps = { name: '名無し' } document.addEventListener('DOMContentLoaded', () => { const container = document.getElementById('app'); createRoot(container).render(<Hello name='やまうちなお' />); (3) })
(1)は、コンポーネントで必要なモジュールをインポートしています。(2)はHTML(この場合はdiv要素)を生成して返す関数Helloを定義します。(3)はそれを呼び出してid属性が'app'である要素に戻り値を設定しています。
(2)の関数中には、<div>要素が直接記述されています。これは、esbuildコマンドの引数に--loaderオプションを加えたため、JSX構文が使用可能になっているからです。HTML中にJavaScriptの式を埋め込むこともできます。
(3)も同様で、JSX構文によって関数HelloをHTML要素として呼び出すことができます。属性にプロパティを与えると、そのまま関数中で参照可能です。
第2回ではJSX構文が使えないという制約があったため、HTMLをJavaScriptコードによって記述していましたが、JSX構文を用いることで記述が直感的に分かりやすくなります。
[NOTE]JSX構文を使うときの注意
JSX構文は便利ですが、通常のHTMLのルールと異なる部分がありますので注意が必要です。主な違いは以下の通りです。
- コンポーネント(タグ)名は大文字から始める必要がある
- class属性はclassNameと書く必要がある(classはJavaScriptのキーワードであるため)
- returnで戻せるHTMLタグは1つのみ(複数戻したい場合は<div>タグなどでくくる)
- 属性の値は必ず二重引用符(" ")でくくる
コンポーネントを読み込む指定は、app/javascipt/application.jsに記述します。このファイルは、アプリケーション共通のエントリポイントであり、esbuildによってビルドされる対象です。importmap-railsを使う場合と異なり、import文で指定するのは論理名でなく実際のパスであることに注意してください。
import "./components/react_hello" # 追加(./は必須)
このファイルは、ビュー(app/views/layouts/application.html.erb)中のjavascript_include_tagsヘルパーメソッドによってscript要素に展開されます。
そして、ビューにReactコンポーネントで更新されるdiv要素を追加しておきます。div要素のid属性の値であるappは、コンポーネント中で指定されていますから、両者は一致する必要があります。
<h1>React#hello</h1> <p>Find me in app/views/react/hello.html.erb</p> <div id="app"></div> # 追加
画像アセットを配置する
最後に、アセットである背景画像を配置します。画像ファイルの置き場所であるapp/assets/imagesにpig2.pngを配置し、app/assets/stylesheets/application.cssに以下のようにスタイルを追記します。このファイルは、アプリケーション共通のCSSファイルであり、ビュー中のstylesheet_link_tagヘルパーメソッドによってlink要素に展開されます。
body { background-image: url('pig2.png'); }
再びbin/devコマンドでPumaサーバを起動して、図のようなページが表示されれば成功です。
バンドラーとアセットパイプライン
最後に、バンドラー(esbuild)が生成するファイルと、それがどのようにアセットパイプライン(Propshaft)で扱われるか見てみましょう。
esbuildは、package.jsonファイルに追記したコマンドの通り、app/javascriptフォルダ以下にあるJavaScriptアセットをビルドした成果物を、app/assets/buildsフォルダに配置します。具体的には、エントリポイントであるapp/javascript/application.jsから、application.jsとapplication.js.mapの2つのファイルがビルドによって作成され、配置されます。これらはアプリケーションの作成直後にすでに存在しますが、ビルドによって常に最新の状態に更新されます。
- app/javascript/application.js ⇒ app/assets/builds/application.js、app/assets/builds/application.js.map
application.js.mapファイルはソースマップであり、esbuildコマンドに--sourcemapオプションを指定したことで作成される、圧縮されたコードを人間が読みやすくするためのファイルです。Google ChromeなどのWebブラウザでデベロッパーツールを使うときに利用されます。
app/assets/buildsフォルダ以下のファイルは、Propshaftの処理対象(app/assetsフォルダ以下)となるので、公開フォルダ(public/assets)へファイル名にダイジェストを付加されてコピーされます。importmap-railsを使ったときはJavaScriptアセットが個別にコピーされましたが、今回はバンドラーを使いますので、1個のJavaScriptファイルと1個のソースマップファイルに集約されます。
- app/javascript/application.js ⇒ application-fcc1d….js
- app/javascript/application.js.map ⇒ application-5a4a2….js.map
スタイルシートや画像アセットについては前回同様です。Propshaftにより、ファイル名にダイジェストが付加されてコピーされ、.cssファイル内のurl()関数の引数も、パスとダイジェスト付きのものに変換されます。
まとめ
今回は、jsbundling-railsとesbuildを使ってReactアプリを開発する過程を通じて、これらのライブラリの目的と機能について紹介しました。次回は、cssbundling-railsを使ってCSSフレームワークをアプリケーションに導入するサンプルを紹介します。