2020.01.02

Spring BootアプリにCreate React Appを導入する


この記事では、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 に追加します。

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.jsonscripts に、postbuild タスクを追加します。npm の機能で、postxxx というタスクを定義すると、xxx タスクの完了後に自動的に実行されます。

frontend/package.json
"postbuild": "node ./postbuild.js"

frontend/build 内のファイルを、src/main/resources/public に移動させるスクリプトです。

frontend/postbuild.js
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 ディレクトリの中身を配信するためのコントローラーを作成します。

SinglePageController.java
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 に一行(★)追加してプラグインをインストールします。

build.gradle
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 を実行するという内容です。

build.gradle
task buildReact(type: YarnTask) {
    execOverrides {
        it.workingDir = './frontend'
    }
    args = ['build']
}

yarn ではなく npm を使用する場合は、上掲のドキュメントを参考に書き換えてください。

さらに、特定の Gradle タスクの前に上記の buildReact タスクを実行したい場合は、以下の記述を追加します。

build.gradle
build.dependsOn buildReact

これは build タスクの実行前に buildReact を実行する、という指定です。


以上、Spring Boot アプリに Create React App を組み合わせてシングルページアプリケーションの環境を構築する方法を紹介しました。