領域展開「Gradle」で Node.js を制する

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200155/aaf1da4f-7269-9428-fb95-51b2c631e875.gif"/>

失礼しました...

# はじめに

オークファンの大きめのデータを担当するバックエンドシステムでは Kotlin + Spring Boot + Gradle の構成がよく採用されています。少し前まではこの構成で作成されるのは、バッチプログラムや Web API がほとんどでしたが、最近さらに Vue.js を追加して UI を持った Single Page Application (SPA) の Web アプリも作成するようになってきました。

Spring Boot のプロジェクトを [Spring Initializr](https://start.spring.io/) で作成すると、Gradle Wrapper が付属しています。これにより、開発環境では JDK のみ用意するだけでプロジェクトのテストやビルドなどをお手軽に実行できていました。

ところが、JavaScript フレームワークである Vue.js がプロジェクトに追加されたことにより、別途 Node.js 環境を用意しなければいけなくなってしまい、これが結構面倒なうえに、プロジェクトメンバーに開発環境を構築してもらう際にトラブルが発生することが少なくありませんでした。

このような場合には Docker にご登場いただくのが一般的ですが、ここではあえて Docker を使わない縛りを課し、すでにプロジェクトに入っている Gradle での問題解決方法を開示していこうと思います。

個人的には Maven や Gradle (Ant は〇ソ...) 支配下の Java や Kotlin でこれまで戦うことが多かったので、npm (yarn は使いやすいですね...) の支配する不慣れな Node.js に対して有利に戦えるといいなと思ったのが発端でした。

プロジェクトの前提は以下としました。

* 開発環境に必要なのは JDK のみ
* Node.js まわりで必要なものは [Gradle Plugin for Node](https://github.com/node-gradle/gradle-node-plugin) で調達
* 幅広い OS (ここでは macOS、Linux、Windows を想定) で開発可能

今回のサンプルプロジェクトの構築は以下の順序で進めていきます。

1. Spring Boot プロジェクトの作成
2. Spring Boot プロジェクトのサブプロジェクト化
3. Vue.js サブプロジェクトの追加
4. Spring Boot と Vue.js のサブプロジェクト間の連携
5. プロジェクトのビルドと実行

サンプルでは Spring Boot と Vue.js を使用しますが、他のものであっても Gradle と Node.js のプロジェクトであれば今回の方法は適用可能なはずです。

コマンドは macOS や Linux の書式で記載しますが、パスの指定をスラッシュ (`/`) からバックスラッシュ (`\`) に変更すれば Windows でも実行可能です。

# Spring Boot プロジェクトの作成

まず Spring Boot の雛形プロジェクトを [Spring Initializr](https://start.spring.io/) で生成します。

https://start.spring.io/ に Web ブラウザでアクセスし、以下のように選択します。(`Group` や `Artifact` などは適宜変更いただいて問題ありません。)

![spring_initializr_01.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200155/bff756db-7f53-6880-b15b-80492e508acd.jpeg)

`GENERATE` ボタンをクリックしてプロジェクトをダウンロードして展開します。

```console
$ unzip spring-boot-vue-app.zip
```

プロジェクトのディレクトリ構成は以下のようになっています。

![spring_boot_vue_app_dir_01_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200155/00b5a0de-71d9-abef-0422-5dc796060d25.png)

初期の Gradle の設定ファイル `settings.gradle.kts` と `build.gradle.kts` は以下の内容になっています。

```settings.gradle.kts
rootProject.name = "spring-boot-vue-app"
```

```build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "2.5.7"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.5.31"
    kotlin("plugin.spring") version "1.5.31"
}

group = "io.aucfan.sample"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "11"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}
```

雛形プロジェクトはフラットに 1 つのプロジェクトのみで構成されていますが、これを以下のサブプロジェクトに分割していきます。

```
spring-boot-vue-app
├── web-flux-server (Spring Boot サブプロジェクト)
│   ├── build.gradle.kts
│   └── src
├── web-vue2-ui (Vue.js サブプロジェクト)
│   └── build.gradle.kts
├── build.gradle.kts
└── settings.gradle.kts
```

# Spring Boot プロジェクトのサブプロジェクト化

初期の Spring Boot プロジェクトを `web-flux-server` サブプロジェクトに移動します。以下のように `web-flux-server` ディレクトリを作成し、`src` ディレクトリを作成したディレクトリ下に移動します。(設定ファイルは YAML 形式で記述したいので、こっそり `application.properties` を `application.yml` に変更しています。)

![spring_boot_vue_app_dir_02_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200155/8e76575c-f4ed-6f52-ebd0-0ac49883ea51.png)

`settings.gradle.kts` の末尾にサブプロジェクトの情報を追記します。

```settings.gradle.kts
rootProject.name = "spring-boot-vue-app"

include(
    "web-flux-server",
)
```

プロジェクト直下の `build.gradle.kts` を以下の内容と、サブプロジェクト下の `web-flux-server/build.gradle.kts` に分割します。変更の概要は次のとおりです。

* `org.springframework.boot` と `io.spring.dependency-management` のプラグインの行の末尾に `apply false` を追記
* `group` と `version` の定義を `allprojects` 内に移動
* `dependencies` 以降を `subprojects` 内に移動
* `java.sourceCompatibility` 行を `subprojects` 内に移動
* `repositories` を `subprojects` 内にもコピー
* `subprojects` 内で Kotlin 関連のプラグインを `apply`
* `web-flux-server` ディレクトリ内にサブプロジェクト用の `build.gradle.kts` を作成
* 親プロジェクト `build.gradle.kts` の `subprojects` から Spring 関連の `dependencies` と `tasks` を `web-flux-server/build.gradle.kts` へ移動
* `web-flux-server/build.gradle.kts` で Spring 関連のプラグインを `apply`
* `web-flux-server/build.gradle.kts` に WebFlux や開発用、デプロイ用の `dependencies` と `tasks` を追加

```build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    // apply false を付加してデフォルトでは術式を発動させないようにする
    id("org.springframework.boot") version "2.5.7" apply false
    id("io.spring.dependency-management") version "1.0.11.RELEASE" apply false
    kotlin("jvm") version "1.5.31"
    kotlin("plugin.spring") version "1.5.31"
}

allprojects {
    group = "io.aucfan.sample"
    version = "0.0.1-SNAPSHOT"
}

repositories {
    mavenCentral()
}

// サブプロジェクト共通設定
subprojects {
    // Kotlin 関連のプラグインを発動させる
    apply(plugin = "kotlin")
    apply(plugin = "org.jetbrains.kotlin.plugin.spring")

    java.sourceCompatibility = JavaVersion.VERSION_11

    repositories {
        mavenCentral()
    }

    dependencies {
        implementation("org.jetbrains.kotlin:kotlin-reflect")
        implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    }

    tasks.withType<KotlinCompile> {
        kotlinOptions {
            freeCompilerArgs = listOf("-Xjsr305=strict")
            jvmTarget = "11"
        }
    }

    tasks.withType<Jar> {
        // JAR ファイル名の基本部分が <プロジェクト名>-<サブプロジェクト名> となるように設定
        archiveBaseName.set(listOf(rootProject.name, project.name).joinToString("-"))
    }
}
```

```web-flux-server/build.gradle.kts
// Spring 関連のプラグインを発動させる
apply(plugin = "org.springframework.boot")
apply(plugin = "io.spring.dependency-management")

dependencies {
    // サブプロジェクトでは developmentOnly がそのままでは呼び出せないので強制召喚
    val developmentOnly = configurations.getByName("developmentOnly")

    // WebFlux に必要
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")

    // 開発ツール
    developmentOnly("org.springframework.boot:spring-boot-devtools")

    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<Test> {
    useJUnitPlatform()
}

// JAR に起動スクリプトを埋め込んで単体で実行可能にする
tasks.withType<org.springframework.boot.gradle.tasks.bundling.BootJar> {
    launchScript()
}
```

# Vue.js サブプロジェクトの追加

Spring Boot プロジェクトをサブプロジェクトに移動できたので、同様に Vue.js サブプロジェクトも追加していきます。

本来であればこちらは Node.js の領域なのですが、Gradle 領域を展開していきます。

Node.js まわりを Gradle で管理するために [Gradle Plugin for Node](https://github.com/node-gradle/gradle-node-plugin) を導入します。

以下のように `web-vue2-ui` ディレクトリを作成し、サブプロジェクト用の `build.gradle.kts` を作成します。

![spring_boot_vue_app_dir_03_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200155/07fdb630-7862-c885-7d52-e57ea38d7e87.png)

`settings.gradle.kts` に `web-vue2-ui` サブプロジェクトを追加します。

```settings.gradle.kts
rootProject.name = "spring-boot-vue-app"

include(
    "web-flux-server",
    "web-vue2-ui",
)
```

親プロジェクト直下の `build.gradle.kts` に [Gradle Plugin for Node](https://github.com/node-gradle/gradle-node-plugin) を `apply false` で追加します。

```build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "2.5.7" apply false
    id("io.spring.dependency-management") version "1.0.11.RELEASE" apply false
    // Gradle Plugin for Node を追加 (デフォルトでは発動させない)
    id("com.github.node-gradle.node") version "3.1.1" apply false
    kotlin("jvm") version "1.5.31"
    kotlin("plugin.spring") version "1.5.31"
}

// (後略)
```

Vue.js サブプロジェクト用の `web-vue2-ui/build.gradle.kts` に以下を記述します。ここでは、`web-vue2-ui/.cache/` ディレクトリ下に Node.js、npm、yarn がダウンロードされて配置されるように設定しています。

```web-vue2-ui/build.gradle.kts
import com.github.gradle.node.NodeExtension
import com.github.gradle.node.npm.proxy.ProxySettings

// Gradle Plugin for Node を発動させる
apply(plugin = "com.github.node-gradle.node")

// サブプロジェクトでは node が呼び出せないので強制召喚
configure<NodeExtension> {
    download.set(true)
    version.set("14.18.1")
    npmVersion.set("6.14.15")
    yarnVersion.set("1.22.17")
    distBaseUrl.set("https://nodejs.org/dist")
    npmInstallCommand.set("ci")
    workDir.set(file("${project.projectDir}/.cache/nodejs"))
    npmWorkDir.set(file("${project.projectDir}/.cache/npm"))
    yarnWorkDir.set(file("${project.projectDir}/.cache/yarn"))
    nodeProjectDir.set(file("${project.projectDir}"))
    nodeProxySettings.set(ProxySettings.SMART)
}
```

Node.js、npm、yarn の本体が `.cache` ディレクトリに取得されるように Gradle Plugin for Node 経由で yarn コマンドを素振りしておきます。

```console
$ ./gradlew :web-vue2-ui:yarn
```

以下のように `nodejs`、`npm`、`yarn` ディレクトリが設定したとおりに作成され、Node.js まわりを閉じ込めることに成功しました。

![spring_boot_vue_app_dir_04_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200155/74131c64-c672-fac4-acee-80a5e4e96f90.png)

これで `yarn` コマンドが呼び出せるようになったので、Vue.js プロジェクトを作成していきます。今回は [Vue CLI](https://cli.vuejs.org/) を使用して Vue.js 2 のシンプルなプロジェクトを作成してみます。一般的には Vue CLI は `global` にインストールすることがほとんどだと思いますが、ここでは使い捨てのプロジェクト内にインストールします。

以下のように `web-vue2-ui` ディレクトリに入って、`yarn` コマンドで使い捨てプロジェクトを作成します。
(Windows の場合は `yarn` のディレクトリが少し異なり、`.cache\yarn\yarn-v1.22.17\yarn` となるようです。)

```console
$ cd web-vue2-ui
$ .cache/yarn/yarn-v1.22.17/bin/yarn init
yarn init v1.22.17
question name (web-vue2-ui): spring-boot-vue-app-ui
question version (1.0.0): 0.0.1
question description: Spring Boot + Vue application
question entry point (index.js): 
question repository url: 
question author: 
question license (MIT): 
question private: 
success Saved package.json
```

作成された使い捨てプロジェクトに Vue CLI を追加します。前述のとおり、ふつうは `yarn global add @vue/cli` とするのですが、Vue CLI が Gradle 領域の外に逃げてしまうので、ここでは `global` を指定していません。

```console
$ .cache/yarn/yarn-v1.22.17/bin/yarn add @vue/cli
```

一時的に `vue` コマンドが使用できるようになったので、以下のようにシンプルな Vue.js 2 のプロジェクトを作成します。

```console
$ ./node_modules/.bin/vue create spring-boot-vue-app-ui

Vue CLI v4.5.15
? Please pick a preset: Default ([Vue 2] babel, eslint)
? Pick the package manager to use when installing dependencies: Yarn
```

`web-vue2-ui/spring-boot-vue-app-ui` 下にプロジェクトが生成されたので、1 階層上に移動させます。その際に不必要になった一時的な使い捨てプロジェクト用の `node_modules`、`package.json`、`yarn.lock` は削除してしまいます。また、作成された Vue.js プロジェクト内に Git 用の隠しディレクトリ `.git` が作成されてしまうので、こちらは移動せずに削除し、`.gitignore` ファイルのみ移動しています。

```console
$ rm -rf node_modules package.json yarn.lock
$ mv spring-boot-vue-app-ui/* .
$ mv spring-boot-vue-app-ui/.gitignore .
$ rm -rf spring-boot-vue-app-ui
```

`.cache` ディレクトリを Git 管理対象外にするために、`web-vue2-ui/.gitignore` の方に定義を追加しておきます。

```web-vue2-ui/.gitignore
.DS_Store
node_modules
/dist
# 以下を追記
/.cache

# (後略)
```

最終的に `web-vue2-ui` サブプロジェクトのディレクトリ構成は以下のようになります。 (作成した Vue.js プロジェクトによって構成は変わる場合があります。)

![spring_boot_vue_app_dir_05_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200155/51141f08-af25-3922-044d-89920a0f90d4.png)

`vue` コマンドで生成された `package.json` の内容は以下です。 

```web-vue2-ui/package.json
{
  "name": "spring-boot-vue-app-ui",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^3.6.5",
    "vue": "^2.6.11"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "babel-eslint": "^10.1.0",
    "eslint": "^6.7.2",
    "eslint-plugin-vue": "^6.2.2",
    "vue-template-compiler": "^2.6.11"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "babel-eslint"
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}
```

開発用のサーバーを起動して動作を確認しておきます。

```console
$ .cache/yarn/yarn-v1.22.17/bin/yarn serve
```

Web ブラウザで http://localhost:8080/ にアクセスすると以下の画面が表示されます。

![vue2_ui_01.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200155/6ebd78ee-93aa-8829-2cf5-a1496545ff67.jpeg)

この後で起動する Spring Boot の Web サーバーもデフォルトでは `8080` ポートを使用するので、衝突を避けるために開発サーバを `Ctrl + C` で停止しておきます。(`./gradlew :web-vue2-ui:yarn_serve` でも開発サーバーは起動できますが、`Ctrl + C` でプロセスが停止できないのでここでは使用していません。)

# Spring Boot と Vue.js のサブプロジェクト間の連携

親プロジェクトのディレクトリに戻り、Git リポジトリの初期化を実行しておきます。

```console
$ cd ..
$ git init
```

`web-vue2-ui/package.json` を以下のように変更します。

```web-vue2-ui/package.json
{
  "name": "spring-boot-vue-app-ui",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve --port 8081 --watch --mode development",
    "build": "vue-cli-service build --dest ../web-flux-server/src/main/resources/static/",
    "lint": "vue-cli-service lint"
  },

  (中略)

}
```

`vue-cli-service serve --port 8081 --watch --mode development` の部分で Vue.js の開発サーバーを設定しています。`--port 8081` でポート番号を `8080` から `8081` に変更して帳を下ろしています。これは前述のとおり Spring Boot の WebFlux サーバーもデフォルトでは `8080` ポートを使用するので、衝突を避けるためです。`--watch` でソースファイルの変更を監視するようにし、`--mode development` で開発環境であることを設定しています。

`vue-cli-service build --dest ../web-flux-server/src/main/resources/static/` の部分では Vue.js のビルドファイルの出力先を、デフォルトの `dist` から Spring Boot サブプロジェクトのリソースディレクトリに変更しています。

また、親プロジェクトのバージョンに合わせて、`version` を `0.0.1` に変更しています。

親プロジェクトの `.gitignore` に以下を追加して、Vue.js ビルドファイルを Git 管理対象外にしておきます。

```.gitignore
# (前略)

# Vue.js build files
/web-flux-server/src/main/resources/static/
```

Gradle Plugin for Node 経由で Vue.js サブプロジェクトをビルドしてみます。

```console
$ ./gradlew :web-vue2-ui:yarn_build
```

Spring Boot サブプロジェクト側の `web-flux-server/src/main/resources/static/` ディレクトリ下に Web ページ用のファイルが生成されます。

![spring_boot_vue_app_dir_06_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200155/be54950e-d6bb-13c9-3661-1b7adf1a45d9.png)

Vue.js サブプロジェクトがビルドできたので、Gradle タスク間の依存関係を設定します。

まず、`yarn build` の前に `yarn install` が実行されるようにします。(毎回 `yarn install` を実行する必要はないのですが、すでに必要なパッケージがインストールされている場合は何もしないので、ここでは気にせずに設定してしまいます。)

```web-vue2-ui/build.gradle.kts
import com.github.gradle.node.NodeExtension
import com.github.gradle.node.npm.proxy.ProxySettings

apply(plugin = "com.github.node-gradle.node")

// サブプロジェクトでは node が呼び出せないので強制召喚
configure<NodeExtension> {
    download.set(true)
    version.set("14.18.1")
    npmVersion.set("6.14.15")
    yarnVersion.set("1.22.17")
    distBaseUrl.set("https://nodejs.org/dist")
    npmInstallCommand.set("ci")
    workDir.set(file("${project.projectDir}/.cache/nodejs"))
    npmWorkDir.set(file("${project.projectDir}/.cache/npm"))
    yarnWorkDir.set(file("${project.projectDir}/.cache/yarn"))
    nodeProjectDir.set(file("${project.projectDir}"))
    nodeProxySettings.set(ProxySettings.SMART)
}

// 以下を追記
// yarn build 前に yarn install を実行する (Gradle Plugin for Node 経由の実行なので _ を付加)
tasks.getByName("yarn_build") {
    dependsOn("yarn_install")
}
```

次に、Spring Boot サブプロジェクトでリソースが処理される前に Vue.js サブプロジェクトをビルドするようにします。

```web-flux-server/build.gradle.kts
import org.springframework.boot.gradle.tasks.bundling.BootJar

apply(plugin = "org.springframework.boot")
apply(plugin = "io.spring.dependency-management")

dependencies {
    // サブプロジェクトでは developmentOnly がそのままでは呼び出せないので強制召喚
    val developmentOnly = configurations.getByName("developmentOnly")
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
    developmentOnly("org.springframework.boot:spring-boot-devtools")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<Test> {
    useJUnitPlatform()
}

tasks.withType<BootJar> {
    launchScript()
}

// 以下を追記
// リソース処理の前に Vue.js サブプロジェクトのビルドを実行
tasks.withType<ProcessResources> {
    dependsOn(":web-vue2-ui:yarn_build")
}
```

これだけでは Spring Boot サブプロジェクト内のリソースに配置されたファイルを公開できないので、`IndexHandler.kt` と `IndexRouterConfiguration.kt` ファイルを以下のように作成します。

![spring_boot_vue_app_dir_07_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200155/0fc68e45-0af8-0df9-b944-d2fec4d4e646.png)

`IndexHandler.kt` には以下の内容を記述します。 

```IndexHandler.kt
package io.aucfan.sample.spring.boot.vue

import org.springframework.beans.factory.annotation.Value
import org.springframework.core.io.Resource
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import org.springframework.web.reactive.function.server.bodyValueAndAwait

@Component
class IndexHandler {

    @Value("classpath:/static/index.html")
    private lateinit var indexHtml: Resource

    suspend fun index(request: ServerRequest): ServerResponse =
        ServerResponse.ok()
            .contentType(MediaType.TEXT_HTML)
            .bodyValueAndAwait(indexHtml)
}
```

`IndexRouterConfiguration.kt` には以下を記述します。

```IndexRouterConfiguration.kt
package io.aucfan.sample.spring.boot.vue

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.io.ClassPathResource
import org.springframework.web.reactive.function.server.RouterFunctions
import org.springframework.web.reactive.function.server.coRouter

@Configuration
class IndexRouterConfiguration {

    @Bean
    fun indexRouter(indexHandler: IndexHandler) = coRouter {
        GET("/", indexHandler::index)
    }

    fun staticResourceRouter() = RouterFunctions.resources("/**", ClassPathResource("static/"))
}
```

# プロジェクトのビルドと実行

以上で準備ができたので、親プロジェクトのディレクトリで (テストを省略して) ビルドを実行します。

```console
$ ./gradlew clean build -x test
```

Spring Boot サブプロジェクトの `web-flux-server` の `build/libs/` ディレクトリ下に `spring-boot-vue-app-web-flux-server-0.0.1-SNAPSHOT.jar` が具現化されます。

![spring_boot_vue_app_dir_08_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200155/e9ee2227-6adc-ff84-a05b-235290603a8d.png)

`spring-boot-vue-app-web-flux-server-0.0.1-SNAPSHOT.jar` を JVM に流し込んで実行してみます。

```console
$ cd web-flux-server/build/libs/
$ java -jar spring-boot-vue-app-web-flux-server-0.0.1-SNAPSHOT.jar
```

Web ブラウザで http://localhost:8080/ にアクセスすると以下の画面が再度表示されます。(この時点では前述の Vue.js の開発用サーバーは http://localhost:8081/ でのアクセスに変更されています。)

![vue2_ui_01.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200155/a7b17f76-1984-742b-5a4a-73ef049fbf34.jpeg)

ここまでのプロジェクト構築では Spring Boot の WebFlux サーバーに何の Web API も実装されていません。ここから Web API を Kotlin で実装し、Vue.js から axios で利用するように開発を進めていくことになります。

# おわりに

今回ご紹介したプロジェクトで作成された Web アプリをデプロイする最もシンプルな方法は以下のような手順になります。

1. 開発環境に JDK を導入
2. プロジェクトを `git clone`
3. プロジェクトのディレクトリで `./gradlew clean build -x test`
4. 作成された JAR を稼働環境に配置
5. 稼働環境に JRE を導入
6. JAR を実行 (サービス化も可能)

実際のプロジェクトではさらに、JAR ファイルの外側から読み込む環境依存の設定ファイルの配置や、Docker イメージにして自動デプロイなどもろもろ追加されていきます。

このように Node.js 関連の儀式を明示的に行うことなく、Gradle 領域内にうまく閉じ込めることができたので、今までどおり心穏やかにデプロイ作業を進めることができています。(とはいえ開発時は Node.js 領域内に入って戦うことになるのですが...)

他にも以下のようなメリットがあるので、今後の SPA プロジェクトでも適している場面があれば採用していこうと思います。

* 開発環境構築時も同様に Node.js 環境を別途用意する必要がない
* Vue.js 2 から Vue.js 3 や Nuxt.js、React、Next.js などに変更する場合も別領域サブプロジェクトを展開して対応可能
* Node.js から見ると領域は外界と隔絶されているので、UI のサブプロジェクト内では異なる Node.js のバージョンを採用可能

<br/>

最後に...

<br/>

劇場版「呪術廻戦 0」
12 月 24 日公開です。ぜひ。