はじめに
こんにちは。バックエンドエンジニアを目指して奮闘中のひろえです。
突然ですが、未経験・独学の身では、何から勉強を始めて、何に重点を置くかって大事ですよね。
私自身は Java
や Spring Boot
から学び、バックエンドに力を入れてきました。
しかし、ポートフォリオを作るうえでもフロントエンドやインフラに関しても最低限メジャーな技術は押さえておきたいところ。
ということで、できるだけ簡単にフロントエンドを始めるべく、希望をざっと挙げてみました。
- フロントエンドはほとんど触ったことがないから最小構成から始めたい
- Vue.js の単一ファイルコンポーネント (SFC) を使ってみたい
- 静的アセットは
jar
ファイルに同梱し、Spring Boot
の静的リソース配信機能を利用して配信したい -
CI/CD
環境や本番環境でもシンプルに環境構築できるようにしたい
希望を叶えるため、あれこれ調べているうちに以下の記事に出会いました。
▼ 領域展開「Gradle」で Node.js を制する
▼ Spring Boot ウェブアプリにフロントエンド技術を含める webpack ビルド
いずれの記事もとても分かりやすく丁寧に説明してくださっているので、こちらを読んでいただければ十分なのですが、フロントエンドに関する知識が少なすぎて詰まる部分があったので、この記事ではよりシンプルな構成で環境構築する方法をご紹介しようと思います。
記事中のソースコードは以下のリポジトリから参照できます。
誤りなどがあればご指摘いただけますと幸いです。
環境
- Java 17.0.5
- Spring Boot 3.2.2
- gradle-node-plugin 7.0.1
- Vue.js 3.4.18
- Node.js 21.6.1
- IntelliJ IDEA 2023.3.3 (Community Edition)
- Windows 11 Pro
プロジェクト構成概要
バックエンドには Gradle
プロジェクト、フロントエンドには Node.js
プロジェクトを使用し、Node.js
プロジェクトは Gradle
プロジェクトのサブプロジェクトとして扱います。
そして gradle-node-plugin というプラグインで両ビルドサイクルを統合する、というのが今回のポイントです。
このプラグインは Gradle
で Node.js
プロジェクトを管理するためのもので、Gradle
のビルドの一環として任意の Node.js
スクリプトを実行させることができます。
今回の場合は、リソースを処理する Gradle
タスクを開始する前に Node.js
プロジェクトのビルドを実行させるように構成することで、バックエンドとフロントエンドのビルドサイクルの統合を実現しています。
なお、gradle-node-plugin
は、ローカルの .gradle
ディレクトリに Node.js
と npm
自動的にインストールする機能も提供しています。
Spring Initializr
で作成した Spring Boot
プロジェクトは Gradle Wrapper
を同梱するため、これによって JDK を用意するだけで済むようになります。
※ Java の Web アプリケーションにおいて静的リソースをどのように構成するかについては、以下の記事が勉強になりました。
▼ Java ウェブアプリプロジェクトに JavaScript/TypeScript などの静的アセットをどう配置するか
今回紹介する構成は、この記事における 「1.1. サーバサイドを Maven/Gradle で、フロントエンドを Maven/Gradle で包んだ npm/Yarn で開発するケース」に該当します。
環境構築
以下のような手順で環境を構築していきます。
1. Spring Boot プロジェクトを作成する
2. Node.js プロジェクトを作成する
3. Vue.js 開発環境を構築する
1. Spring Boot プロジェクトを作成する
はじめに Spring Initializer で Spring Boot プロジェクトを生成します。
ビルドツールは Gradle - Groovy
を、言語は Java
を選択します。
依存関係は最低限 spring-boot-starter-web
と spring-boot-starter-thymeleaf
が追加されていれば問題ありません。
メタデータについては必要に応じて変更してください。
GENERATE ボタンをクリックしてプロジェクトをダウンロードします。
ダウンロードが完了したら、zip ファイルを展開し、お好みのエディタで開いてください。
以下のようなディレクトリ構成となっていると思います。
settings.gradle
と build.gradle
は以下のような状態です。
rootProject.name = 'demo'
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.2'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
2. Node.js プロジェクトを作成する
続いては Node.js
プロジェクトを作成します。
2.1 フロントエンドサブプロジェクトを作成する
Node.js
プロジェクトは Gradle
サブプロジェクトとして扱うため、まずはそのための設定を行いましょう。
まず、プロジェクトルートに新規ディレクトリを作成します。
ここではディレクトリ名を frontend としますが、お好きな名前にしていただいて構いません (その場合は以降適宜読み替えてください)。
ディレクトリの作成が完了したら、Gradle
にサブプロジェクトとして認識させるために settings.gradle
ファイルを以下のように変更します。
rootProject.name = 'demo'
+ include 'frontend'
※ Gradle のマルチプロジェクトに関しては以下をご参照ください。
▼ Multi-Project Build Basics (gradle.org)
2.2 gradle-node-plugin を追加する
つぎに、frontend ディレクトリ直下に frontend サブプロジェクト専用の build.gradle
ファイルを作成してください。
frontend サブプロジェクトにおいて gradle-node-plugin
を利用できるようにするために、この build.gradle
ファイルを以下のように編集します。
plugins {
id("com.github.node-gradle.node") version "7.0.1"
}
// プロジェクトローカルに Node.js をダウンロードする
node {
download.set(true)
version.set("21.6.1")
}
node
ブロックでは、frontend ディレクトリ配下に指定したバージョンの Node.js
(とそれにバンドルされた npm
) をダウンロードする設定をしています。
他にも設定可能な項目がいくつかありますので、詳細については以下をご参照ください。
▼ gradle-node-plugin/docs/usage.md at 7.0.2 (github.com)
追記
frontend サブプロジェクトの build.gradle には、以下も記述するように書いていましたが、実際には npmInstall
タスクが npm_run_build
よりも先に実行されるため、この設定は不要でした。
// npm run build の前に npm install を実行する
tasks.getByName("npm_run_build") {
dependsOn("npm_install")
}
実行順序の確認には、次のコマンドを利用しました。
$ ./gradlew npm_run_build --dry-run
:frontend:nodeSetup SKIPPED
:frontend:npmSetup SKIPPED
:frontend:npmInstall SKIPPED
:frontend:npm_run_build SKIPPED
追記ここまで
2.3 バックエンドとフロントエンドのビルドプロセスを統合する
続いて、ルートプロジェクトの build.gradle
ファイルに以下を追加してください。
tasks.named('processResources') {
dependsOn(":frontend:npm_run_build")
}
これにより、ルートプロジェクトにおいてリソース処理を開始する前に frontend サブプロジェクトのビルドが実行されるようになります。
2.4 Node.js プロジェクトとして初期化する
最後に frontend サブプロジェクトを Node.js プロジェクトとして初期化します。
frontned ディレクトリに移動して以下のコマンドを実行してください。
$ npm init -y
コマンドの実行が完了すると、以下のような内容の package.json
が自動的に作成されているかと思います。
{
"name": "frontend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
}
3. Vue.js 開発環境を構築する
それでは、Vue.js
開発環境を構築していきましょう。
Vue.js で単一ファイルコンポーネントを使用するためには、Webpack
などのビルドツールを導入する必要があるため、Webpack
-> Vue.js
の順で設定を行います。
ここでも引き続き frontend ディレクトリで作業してください。
3.1 Webpack をインストールする
まず、以下のコマンドを実行し、Webpack
と webpack-cli
をインストールします。
$ npm install --save-dev webpack webpack-cli
インストールが完了すると node_modules/
ディレクトリと package-lock.json
ファイルが新たに作成されているかと思います。
また、package.json
ファイルには devDependencies
ブロックが追記されていると思うので確認してください。
"devDependencies": {
"webpack": "^5.90.1",
"webpack-cli": "^5.1.4"
}
このファイルには npm run build
コマンドで Webpack
のビルドを実行させるために、scripts
ブロックに build
スクリプトを追加しておきます (test スクリプトは削除しました)。
"scripts": {
"build": "webpack"
}
つぎに frontend ディレクトリ直下に webpack.config.js
ファイルを作成し、以下のように編集してください。
const Webpack = require('webpack');
const path = require('path');
module.exports = {
mode: 'development',
output: {
// ビルド成果物はルートプロジェクトの src/main/resources/static/dist に出力する
path: path.resolve(__dirname, '../src/main/resources/static/dist'),
// Webpack によって生成される各バンドルのファイル名 ( [name] はエントリポイント名で置き換え)
filename: 'js/[name].bundle.js',
// ビルド成果物を出力する前に、出力ディレクトリをクリーンアップする
clean: true,
},
resolve: {
alias: {
// .js ファイルや .vue ファイル内で @src エイリアスを使用して src ディレクトリを参照する
'@src': path.resolve(__dirname, 'src/'),
},
},
};
Node.js
プロジェクト (frontend サブプロジェクト) のビルド成果物を Gradle
がリソースとして認識するディレクトリ src/main/resources/static/dist/
に出力するように設定しているのがポイントです。
なお、node_modules/
と Node.js
プロジェクトのビルド成果物に関しては、必要に応じて .gitignore
に以下を追記して Git 管理から除外してください。
frontend/node_modules
src/main/resources/static/dist
3.2 Vue.js をインストールする
Webpack
の設定が完了したので、続いて Vue.js
の設定を行っていきましょう。
以下のコマンドを frontend ディレクトリで実行してください。
$ npm install vue
$ npm install --save-dev vue-loader vue-template-compiler vue-style-loader css-loader
インストールが完了すると package.json
の依存関係が以下のように変更されています。
"devDependencies": {
"css-loader": "^6.10.0",
"vue-loader": "^17.4.2",
"vue-style-loader": "^4.1.3",
"vue-template-compiler": "^2.7.16",
"webpack": "^5.90.1",
"webpack-cli": "^5.1.4"
},
"dependencies": {
"vue": "^3.4.18"
}
webpack.config.js
を以下のように編集して、Vue.js
のための基本的な設定を行います。
const Webpack = require('webpack');
const path = require('path');
+ const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
mode: 'development',
+ entry: {
+ // 共通のライブラリなどは vendor.bundle.js としてひとまとめにする
+ vendor: ['vue'],
+ },
output: {
// ビルド成果物はルートプロジェクトの src/main/resources/static/dist に出力する
path: path.resolve(__dirname, '../src/main/resources/static/dist'),
// Webpack によって生成される各バンドルのファイル名 ([name]はエントリーポイントの名前で置き換え)
filename: 'js/[name].bundle.js',
// ビルド成果物を出力する前に、出力ディレクトリをクリーンアップする
clean: true,
},
+ plugins: [
+ // .vue ファイルをバンドルするために必要なプラグイン
+ new VueLoaderPlugin(),
+ ],
+ module: {
+ rules: [
+ // .vue ファイルをビルドおよびバンドルする
+ {
+ test: /\.vue$/,
+ loader: 'vue-loader',
+ },
+ // .vue ファイル内の style をビルドおよびバンドルする
+ {
+ test: /\.vue\.css$/,
+ use: ['vue-style-loader', 'css-loader'],
+ },
+ ],
+ },
resolve: {
alias: {
+ // ESM (ECMAScript Modules) のバンドラー向けビルドを使用する
+ vue$: 'vue/dist/vue.esm-bundler.js',
// .js ファイルや .vue ファイル内で @src エイリアスを使用して src ディレクトリを参照する
'@src': path.resolve(__dirname, 'src/'),
},
},
};
適宜コメントを入れましたが、詳細な説明を省略したので、不明な点があれば以下の記事がおすすめです。
▼ Webpack 5 を使った vue.js の環境構築と SFC の利用方法
こちらの記事は、単一ファイルコンポーネントを利用する場合の Vue.js の開発環境構築手順が丁寧に説明されていて分かりやすかったです。
なお、Babel や Jest などの他のライブラリについても同じ手順で設定可能なので、ぜひやってみてください。
環境構築は以上となります。
以降は、この環境における単一ファイルコンポーネントの利用方法について簡単に説明していきます。
おまけ. アプリケーション作成
まず、ルートプロジェクトの src/main/java/
ディレクトリ配下の適切なパッケージにコントローラークラスを定義してください。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
@GetMapping
public String display() {
return "index";
}
}
同じくルートプロジェクトの src/main/resources/templates/
ディレクトリ直下に index.html
ファイルを作成します。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="main"><example/></div>
<script th:src="@{/dist/js/vendor.bundle.js}"></script>
<script th:src="@{/dist/js/index.bundle.js}"></script>
</body>
</html>
以降は frontend サブプロジェクトでの作業です。
まず、エントリーポイントとなる JavaScript
ファイルを作成します。
frontend ディレクトリ直下に src/main/
ディレクトリを作成し、以下のような内容の index.js
ファイルを作成してください。
import { createApp } from 'vue';
import example from '@src/main/components/example.vue';
createApp({
components: {
example: example,
},
}).mount('#main');
つぎに、このファイルでインポートされている単一ファイルコンポーネントを作成します。
同じく src/main/
ディレクトリの直下に components/
ディレクトリを作成し、以下のような example.vue
ファイルを作成してください。
<template>
<div class="message">{{ message }}</div>
</template>
<script>
export default {
data () {
return {
message: 'Hello Vue Component'
}
}
}
</script>
<style scoped>
.message {
font-weight: bold;
}
</style>
以上です。
最終的なディレクトリ構成は以下の通りです。
それでは、ルートプロジェクトにおいて、以下のコマンドを実行してアプリケーションを実行しましょう。
$ ./gradlew bootRun
Node.js プロジェクトのビルドに成功すると、ルートプロジェクトの src/main/resources/static/dist/js/
ディレクトリに index.bundle.js
と vendor.bundle.js
が生成されているはずです。
ブラウザで http://localhost:8080 にアクセスして、以下のように表示されれば成功です。
さいごに
今回はシンプルな構成、簡単な環境構築にこだわってみましたが、パフォーマンスはどうなんだろうとか、ファイルサイズは削減できるところはないだろうかとか、悩みだしたらきりがないですね。
メリデメを考慮して環境構築ができるようになりたいものです。
最後までお読みいただき、ありがとうございました!
追記
SPA バージョンの記事も書きました!
よろしければ、こちらもどうぞ。