SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Railsによるクライアントサイド開発入門

Rails 7における、バンドラーを用いたReactアプリ開発~jsbundling-railsとesbuild~

Railsによるクライアントサイド開発入門 第3回

  • X ポスト
  • このエントリーをはてなブックマークに追加

環境が整ったら、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ファイルに追記します。

リスト 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の実行がその内容になっていますが、引数やオプションの意味について、標準では指定されていないものも含めて表にまとめておきます。

表 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アクションに変更しておきましょう。

リスト config/routes.rb
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を見てみましょう。

リスト 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として作成します。

リスト 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文で指定するのは論理名でなく実際のパスであることに注意してください。

リスト app/javascipt/application.js
import "./components/react_hello"		# 追加(./は必須)

 このファイルは、ビュー(app/views/layouts/application.html.erb)中のjavascript_include_tagsヘルパーメソッドによってscript要素に展開されます。

 そして、ビューにReactコンポーネントで更新されるdiv要素を追加しておきます。div要素のid属性の値であるappは、コンポーネント中で指定されていますから、両者は一致する必要があります。

リスト app/views/react/hello.html.erb
<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要素に展開されます。

リスト app/assets/stylesheets/application.css
body {
    background-image: url('pig2.png');
}

 再びbin/devコマンドでPumaサーバを起動して、図のようなページが表示されれば成功です。

図 完成したReactアプリケーション
図 完成したReactアプリケーション

バンドラーとアセットパイプライン

 最後に、バンドラー(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フレームワークをアプリケーションに導入するサンプルを紹介します。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
Railsによるクライアントサイド開発入門連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 山内 直(WINGSプロジェクト ヤマウチ ナオ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook <個人紹介>WINGSプロジェクト所属のテクニカルライター。出版社を経てフリーランスとして独立。ライター、エディター、デベロッパー、講師業に従事。屋号は「たまデジ。」。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/16059 2022/07/08 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング