SpringBoot上でVueを動かしてみた

# はじめに
SpringBootで作ったWebアプリのフロントエンドにVueを導入してみます。
今回やることとして、SpringBootアプリでVueのwelcomeページを動かしてみます。

# 環境
OS：macOS Ventura 13.6.3
Java：17
SpringBoot：3.2.1
yarn：1.22.21
nodenv：1.4.1
node：16.13.2
vue：2

# 全体像
```shell
springboot-vue（SpringBootプロジェクト）
├── src/main
│   ├── java
│   │   └── com.example.sample（パッケージ）
│   │         ├── Controller.java
│   │         └── Application.java
│   │  
│   └── resources
│          ├── static（buildの出力先）
│          │    ├── css
│          │    │    └── app.css
│          │    ├── js
│          │    │    ├── app.js
│          │    │    ├── app.js.map
│          │    │    ├── chunk-vendors.js
│          │    │    └── chunk-vendors.js.map
│          │    │
│          │    └── favicon.ico
│          │
│          ├── templates
│          │    └── index.html
│          │
│          └── application.yml
│
├── vue（vueプロジェクト）
│   ├── node_modules
│   ├── public
│   │    ├──favicon.ico
│   │    └──index.html
│   │
│   ├── src
│   │    ├── assets
│   │    │     └──logo.png
│   │    │
│   │    ├── components
│   │    │     └──HelloWorld.vue
│   │    │
│   │    ├── App.vue
│   │    └── main.js
│   │
│   ├── jsconfig.json
│   ├── package.json
│   ├── vue.config.js
│   └── yarn.lock
│
├── build.gradle
├── gradlew
├── gradlew.bat
└── settings.gradle
```

# SpringBootプロジェクトを作成
まず、[spring initializr](https://start.spring.io/)でSpringBootプロジェクトを作成します。
Dependenciesでは、以下を選択しておきます。
・Thymeleaf
・Spring Web
・Spring Boot Devtools

## Controllerを実装
続いて、Controllerを実装します。
実装内容は後述で実装するindex.htmlをテンプレートに指定するだけです。

```Controller.java
package com.example.sample;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping
@Controller
public class Controller {

  @GetMapping
  public String index() {
    return "index";
  }

}

```

## Thymeleafテンプレートを仮実装
bodyタグ等なく、空で仮実装しておきます。

```index.html
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
</html>
```

## 作成結果

```shell
springboot-vue（SpringBootプロジェクト）
├── src/main
│   ├── java
│   │   └── com.example.sample（パッケージ）
│   │         └── Controller.java
│   │  
│   └── resources
│          ├── templates
│          │    └── index.html
│          │
│          └── application.yml
│       
├── build.gradle
├── gradlew
├── gradlew.bat
└── settings.gradle
```

# vueプロジェクトを作成
続いてSpringBootプロジェクトの中にvueプロジェクトを作成します。
vueプロジェクトの作成は、[こちらの記事](https://qiita.com/kaburankattara/items/1e341bb8830ddcd9b68e)をベースにします。

```shell
springboot-vue $ vue create vue
```

## 作成結果

```shell
springboot-vue（ルートプロジェクト）
└── vue（vueプロジェクト）
    ├── node_modules
    ├── public
    │    ├──favicon.ico
    │    └──index.html
    │
    ├── src
    │    ├── assets
    │    │     └──logo.png
    │    │
    │    ├── components
    │    │     └──HelloWorld.vue
    │    │
    │    ├── App.vue
    │    └── main.js
    │
    ├── jsconfig.json
    ├── package.json
    ├── vue.config.js
    └── yarn.lock
```
# vueの設定変更 & build
## buildの出力先を変更
デフォルトのままだと、buildの出力先はvueプロジェクト直下のdistディレクトリとなります。
SpringBootのWebアプリ上で実行させるため、SpringBootプロジェクト内の「resource/static」に変更します。

出力先を変更するには、package.jsonのbuildコマンドに--destオプションを追加して指定します。

```package.json
{
  ・・・
  "scripts": {
    ・・・
    "build": "vue-cli-service build --dest ../src/main/resources/static/",
    ・・・
  },
```

## キャッシュバスティングをOFFにする
vueのデフォルト設定ではキャッシュバスティングが有効になっており、ファイル名にハッシュ値が付与されてしまいます。

```shell
app.js
↓
app.480b8b8a.js
```

このままでは、build後のJavaScriptファイルをThymeleafテンプレートで指定しづらいです。
そのため、キャッシュバスティングをoffに設定しておきます。

vue.config.jsにfilenameHashingをfalseで設定出来ます。

```vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  filenameHashing: false,
  transpileDependencies: true
})
```

## buildを実行
buildの出力先を変更出来たら、buildを実行します。

```shell
springboot-vue $ cd vue
vue $ yarn build
```

「resources/static」内にJavaScript、CSSファイルが出力されていれば成功です。

```shell
springboot-vue（SpringBootプロジェクト）
└── src/main
    └── resources
           └── static（buildの出力先）
                ├── css
                │    └── app.css
                └── js
                     ├── app.js
                     ├── app.js.map
                     ├── chunk-vendors.js
                     └── chunk-vendors.js.map
```

# SpringBootの設定変更
## キャッシュバスティングをONにする
vue側でキャッシュバスティングをOFFにした代わりにSpringBoot側でキャッシュバスティングをONに設定します。

```application.yml
spring:
  web:
    resources:
      chain:
        strategy:
          content:
            enabled: true
            paths: /**
```

# Thymeleafテンプレートを実装
vueプロジェクト作成時に自動生成されたindex.htmlの内容をThymeleafテンプレートに実装します。

```index.html
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link rel="icon" href="/favicon.ico">
    <title>vue</title>
    <script defer="defer" th:src="@{/js/chunk-vendors.js}"></script>
    <script defer="defer" th:src="@{/js/app.js}"></script>
    <link th:href="@{/css/app.css}" rel="stylesheet">
  </head>
  <body>
    <noscript>
        <strong>We're sorry but vue doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
  </body>
</html>
```

script、linkタグではbuildで生成したファイルを指定します。

# 動作確認
Application.javaを実行し、SpringBootアプリを起動します。
ブラウザから http://localhost:8080/ にアクセスし、vueのwelcomeページが表示され、かつブラウザのDevToolsでhtmlを確認し、JavaScript、CSSファイルがキャッシュバスティングされていれば成功です。

# 参考文献
https://cli.vuejs.org/guide/html-and-static-assets.html#disable-index-generation

https://cli.vuejs.org/guide/cli-service.html#vue-cli-service-build

https://spring.pleiades.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties.core