Spring Boot + Vue.js (SPA)アプリの構築方法

この記事ではSpring BootとVue.js (SPA)アプリの構築方法を紹介したいと思います。本アプリはAPIサーバーとフロントエンドは同一サーバーで稼働する構成を想定しています。

アプリケーション全体のソースコードはこちら

環境

Spring Boot2.5.4
Java16
Kotlin1.5.30
Vue CLI4.5.13
Vue3.2.6
vue-router4.0.11
TypeScript4.3.5

spring initializrで以下の構成で作成されたアプリケーションを前提としています。

設定

それでは順に設定を進めていきましょう!

Vueのソースコードの追加

まずはVue関連のソースコードを追加していきましょう。
本アプリではVueのソースコードをsrc/main/vue以下に配置し、package.jsonなどの設定ファイルはプロジェクト直下に配置するようにします。

babel.config.js

babel.config.jsをプロジェクト直下に配置します。

module.exports = {
    presets: [
        "@vue/cli-plugin-babel/preset"
    ]
}
package.json

package.jsonをプロジェクト直下に追加します。

{
  "name": "spring-boot-vue",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "axios": "^0.21.1",
    "core-js": "^3.17.2",
    "vue": "^3.2.6",
    "vue-router": "^4.0.11",
    "vuex": "^4.0.2"
  },
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^4.30.0",
    "@typescript-eslint/parser": "^4.30.0",
    "@vue/cli-plugin-babel": "~4.5.13",
    "@vue/cli-plugin-eslint": "~4.5.13",
    "@vue/cli-plugin-router": "~4.5.13",
    "@vue/cli-plugin-typescript": "~4.5.13",
    "@vue/cli-plugin-vuex": "~4.5.13",
    "@vue/cli-service": "~4.5.13",
    "@vue/compiler-sfc": "^3.2.6",
    "@vue/eslint-config-standard": "^5.1.2",
    "@vue/eslint-config-typescript": "^7.0.0",
    "eslint": "^6.7.2",
    "eslint-plugin-import": "^2.20.2",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-standard": "^4.1.0",
    "eslint-plugin-vue": "^7.17.0",
    "sass": "^1.26.5",
    "sass-loader": "^8.0.2",
    "typescript": "~4.3.5"
  }
}
tsconfig.json

tsconfig.jsonをプロジェクト 直下に追加します。

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [
      "webpack-env"
    ],
    "paths": {
      "@/*": [
        "src/main/vue/*"
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "src/main/vue/**/*.ts",
    "src/main/vue/**/*.tsx",
    "src/main/vue/**/*.vue",
    "src/test/vue/**/*.ts",
    "src/test/vue/**/*.tsx",
    "src/test/vue/**/*.vue",
  ],
  "exclude": [
    "node_modules"
  ]
}

paths及びincludeの設定部分でVueのソースファイルの位置を指定しています。

vue.config.js

vue.config.jsをプロジェクト 直下に追加します。
vue.config.jsはVue CLIの設定を行うための設定ファイルとなります。

const path = require("path")

module.exports = {
    configureWebpack: {
        resolve: {
            alias: {
                "@": path.join(__dirname, "/src/main/vue"), // (1) エイリアスの設定
            },
        },
    },

    outputDir: "src/main/resources/static", // (2) ビルド結果の出力先

    pages: {
        index: {
            entry: "src/main/vue/main.ts",
            template: "src/main/vue/public/base.html",
            filename: "index.html",
        },
    },

    // (3) 開発サーバーの設定
    devServer: {
        proxy: {
            "/api": {
                target: "http://localhost:8080",
            },
        },
        port: 8090,
    },
}

(1) ではimport文等で使用可能なエイリアスの参照先を設定しています。

(2)は非常に重要な設定なのですが、Vue CLIのビルドの出力先を指定しています。出力先をsrc/main/resources/staticにすることによりSpring Bootアプリの静的ファイルとして公開されます。

(3)では開発サーバーの設定をしています。
Vue CLIでは開発用のサーバーが内蔵されており、開発時はそちらを使用することでソースコードの変更を即時反映することができます。Spring Bootサーバのポートと重複しないように8090ポートで起動するように設定しています。
また、開発環境では”api”で始まるURIの通信はSpring BootのRestControllerにリクエストを送れるようにプロキシ設定を行っています。

SPA用の画面遷移設定

本アプリケーションはVueのrouterを使用しており、いわゆるSPA(Single Page Application)の構成になっているため、サーバーサイド(Spring Boot)ではページ要求に対して常にindex.htmlを返す必要があります。

index.html遷移用のコントローラーの設定
package com.example.springbootvue.controller

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

@Controller
class ForwardController {
    @GetMapping("/")
    fun index() = "forward:/index.html"
}
ResourceHandlerの設定

次に/以外のパスでアクセスがあった場合もindex.htmlを返せるようにします。

まずはSpring MVCの設定を行います。
WebMvcConfigurerを継承した設定クラスを作成し、addResourceHandlersメソッドをオーバーライドし、SPA用のResourceResolverを登録します。

package com.example.springbootvue.config

import org.springframework.boot.autoconfigure.web.WebProperties
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer


@Configuration
class WebMvcConfig(
    private val resources: WebProperties.Resources,
    private val spaPageResourceResolver: SpaPageResourceResolver,
) : WebMvcConfigurer {

    override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
        registry.addResourceHandler("/**")
            .addResourceLocations(*resources.staticLocations)
            .resourceChain(resources.chain.isCache)
            .addResolver(spaPageResourceResolver)
    }
}
package com.example.springbootvue.config

import org.springframework.core.io.Resource
import org.springframework.stereotype.Service
import org.springframework.web.servlet.resource.PathResourceResolver

@Service
class SpaPageResourceResolver: PathResourceResolver() {
    override fun getResource(resourcePath: String, location: Resource): Resource? {
        val resource = super.getResource(resourcePath, location)
        return resource ?: super.getResource("index.html", location)
    }
}

上記の設定ではresourcePathで渡されたパスが存在しない場合にindex.htmlを返すようにしています。

次にcssやjsなどの静的リソースはSPA用のResourceResolverが適用されないようにapplication.ymlで静的リソースのパスパターンを登録します。

spring:
  mvc:
    static-path-pattern: /**/*.* # 拡張子付きのものは静的リソースとして扱う

build.gradleの設定

ビルド設定を行います。

本アプリではVueのアプリケーションの構築にVue CLIを使用しており、Vue CLIではアプリケーションのビルドにWebpackというビルドツールを使用しています。上記の設定ではGradle Plugin for Nodeというプラグインを使用し、gradleのビルドプロセスにVue CLIのビルドプロセスを組み込んでいます。

plugins {
    id "org.springframework.boot" version "2.5.4"
    id "io.spring.dependency-management" version "1.0.11.RELEASE"
    id "org.jetbrains.kotlin.jvm" version "1.5.30"
    id "org.jetbrains.kotlin.plugin.spring" version "1.5.30"
    id "com.github.node-gradle.node" version "3.1.0" // (1) Gradle Plugin for Nodeを追加
}

group = "com.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_16

repositories {
    mavenCentral()
}

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

compileKotlin {
    kotlinOptions {
        freeCompilerArgs = ["-Xjsr305=strict"]
        jvmTarget = "16"
    }
}

compileTestKotlin {
    kotlinOptions {
        freeCompilerArgs = ["-Xjsr305=strict"]
        jvmTarget = "16"
    }
}

test {
    useJUnitPlatform()
}

// (2)
node {
    version = "14.17.6" 
    npmVersion = "7.13.0"
    download = true
    workDir = file("${project.projectDir}/node_runtimes/nodejs")
    npmWorkDir = file("${project.projectDir}/node_runtimes/npm")
    nodeModulesDir = file("${project.projectDir}")
}

// (3)
processResources.dependsOn npm_run_build
(1) Gradle Plugin for Nodeをプラグインとして追加
(2) Vue CLIで使用するNode.jsを設定

downloadをtrueにすることでNode.jsやnpmがインストールされていない環境でも自動的にランタイムをダウンロードし、実行されます。

(3) gradleのリソースプロセスにVue CLIのビルドプロセスを組み込む

上記の設定ではprocessResourcesの実行前に npm_run_buildタスクが実行されます。
Gradle Plugin for Nodeではスネークケースでnpmのコマンドを実行できますので、npm_run_buildと指定することで、npm run buildが実行されます。

まとめ

今回はSpring BootとVue.js (SPA)アプリの構築方法を紹介しました。機会があれば、React (Create React Appベース)のアプリの構築方法もご紹介できればと思います。

最後までお読みいただき、ありがとうございました。

コメント

タイトルとURLをコピーしました