この記事では、Spring Boot アプリに Create React App を組み合わせて SPA の基盤を作成する方法を紹介します。
SPA としての配信するには、フロントエンドとサーバーサイドを完全に分けて、Nginx などでプロキシする方法もありますが、今回紹介するのは、よりインフラの設定が少なくて済む、React を Spring Boot から配信する方法です。
Create React App 導入
Spring Boot
まずは普通に、Spring Initializr で Spring Boot アプリが作成されていることを前提とします。
spring-react
├─ src
│ ├─ main
│ └─ test
├─ build.gradle
└─ etc...
フロントエンドを追加
ここに、以下の構成になるように、フロントエンドコードを追加します。
spring-react
├─ frontend
│ ├─ src
│ ├─ node_modules
│ ├─ package.json
│ └─ etc...
├─ src
│ ├─ main
│ └─ test
├─ build.gradle
└─ etc...
プロジェクトルートで、以下のコマンドを実行して React アプリを作成します。
$ npx create-react-app frontend
frontend
ディレクトリに移動して yarn start
コマンドを実行すると、localhost:3000
で React アプリが起動します。
$ cd frontend
$ yarn start
開発時の設定
プロキシ
マニュアルに従って、フロントエンドからサーバサイドにアクセスするための設定を package.json
に追加します。
"proxy": "http://localhost:8080"
開発時はフロントは localhost の 3000 番ポート、サーバサイドは 8080 番ポートで起動しているので、フロントエンドからの HTTP リクエストを 8080 番に向けてやるための設定です。
本番ビルド
開発環境が整いましたので、本番環境にデプロイするためのビルドの準備も行いましょう。
フロントエンド
いまの設定で yarn build
を実行すると、以下のように frontend
ディレクトリにビルド結果が出力されます。しかし、この場所に出力されても Spring Boot アプリから配信することができません。
spring-react
├─ frontend
│ ├─ build ★
│ ├─ src
│ ├─ node_modules
│ ├─ package.json
│ └─ etc...
├─ src
├─ build.gradle
└─ etc...
残念ながら、Create React App には出力先をカスタマイズするオプションはありません。そこで、build
コマンド実行後にビルド結果を Spring Boot から配信できる位置に移動させるスクリプトを書いてしまいます。
package.json
の scripts
に、postbuild
タスクを追加します。npm の機能で、postxxx
というタスクを定義すると、xxx
タスクの完了後に自動的に実行されます。
"postbuild": "node ./postbuild.js"
frontend/build
内のファイルを、src/main/resources/public
に移動させるスクリプトです。
const path = require('path');
const fs = require('fs-extra');
const BUILD_DIR = path.join(__dirname, './build');
const PUBLIC_DIR = path.join(__dirname, '../src/main/resources/public');
fs.emptyDirSync(PUBLIC_DIR);
fs.copySync(BUILD_DIR, PUBLIC_DIR);
src/main/resources/public
はビルド結果なので .gitignore
に含めていいでしょう。
ルーティング
次に、public
ディレクトリの中身を配信するためのコントローラーを作成します。
package com.hypertextcandy.react;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class SinglePageController {
@GetMapping("/**/{path:[^.]*}")
public String any() {
return "forward:/index.html";
}
}
どんな URL でリクエストが来ても、index.html
を返却するコントローラーです。SPA においては画面側のルーティング(URL と表示内容の紐づけ)はフロントエンドで行うので、サーバーサイドは index.html
をレスポンスすれば OK というわけです。
Gradle
このままでは、最終的にアプリをビルドする際、フロントエンド用に yarn のビルドコマンドと、サーバサイド用に Gradle のビルドコマンドを2つ実行しなければいけません。
それはそれで問題はないのですが、Gradle からフロントエンドのビルドもまとめて行える設定を紹介します。
Gradle から yarn コマンドを呼び出すため、gradle-node-plugin を使用します。plugins
に一行(★)追加してプラグインをインストールします。
plugins {
id 'org.springframework.boot' version '2.2.2.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
id "com.moowork.node" version "1.3.1" // ★ 追加
}
gradle-node-plugin のドキュメントを参考に、以下のタスクを追記します。作業ディレクトリを ./frontend
に移し、yarn build
を実行するという内容です。
task buildReact(type: YarnTask) {
execOverrides {
it.workingDir = './frontend'
}
args = ['build']
}
yarn ではなく npm を使用する場合は、上掲のドキュメントを参考に書き換えてください。
さらに、特定の Gradle タスクの前に上記の buildReact
タスクを実行したい場合は、以下の記述を追加します。
build.dependsOn buildReact
これは build
タスクの実行前に buildReact
を実行する、という指定です。
以上、Spring Boot アプリに Create React App を組み合わせてシングルページアプリケーションの環境を構築する方法を紹介しました。