はじめに
Java/Spark プロジェクトの Gradle を利用したビルド/デプロイについて、私が試したことをまとめたものです。
(ここで言うSpark
は、分散処理ライブラリのApache Spark
ではなく、Java の Sinatra ライクな Microframework のSpark
のことです)
目次
- Hello World アプリ
- jade テンプレートエンジン
- Grunt/bower による Web パッケージのビルド
- 付録 A. TravisCI によるビルド
- 付録 B. Gradle で docker build
- 付録 C. TravisCI から DockerHub へ docker push
各章でのソースコードは Commits · kaakaa/gradle-frontend-boilerplate から確認できます。
環境
- Java 1.8.0_74
- Gradle 2.12
- spark-core 2.3
- spark-template-jade 2.3
- node.js 4.4.0
gradle-frontend-boilerplate
3. Grunt/bower による Web パッケージのビルド
概要
Gradle のビルドフローにnpm
,grunt
,bower
を組み込む。
厳密には、Gradle プラグインcom.moowork.grunt
を使ってnpm install
と Grunt タスクを実行するだけ。bower
は Grunt タスクとして操作する。
Java プロジェクトから bootstrap などのフロントエンド資産を利用する場合、WebJars - Web Libraries in Jarsを利用するなどの方法もあるが、今回はフロントエンドツールに慣れるために、なるべくフロントエンド部分は Gradle から実行するだけにしたかったからである.
実行
git clone https://github.com/kaakaa/gradle-frontend-boilerplate.git
cd gradle-frontend-boilerplate
git checkout sec_3
./gradlew run
ビルドスクリプト
plugins {
id 'java'
id 'application'
id 'com.moowork.grunt' version '0.11' // (1) - npm/gruntを操作するGradleプラグインを指定する
id 'com.moowork.node' version '0.11'
}
group 'org.kaakaa'
version '1.0-SNAPSHOT'
/** For Java build */
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
mainClassName = 'org.kaakaa.spark.Main'
sourceSets { // (2) - フロントエンドビルドの成果物をJavaビルドのリソースに組み込む
main {
resources {
srcDir 'src/main/resources'
srcDir 'src/main/web/bower_components'
}
}
}
repositories {
mavenCentral()
}
dependencies {
compile 'com.sparkjava:spark-core:2.3'
compile 'com.sparkjava:spark-template-jade:2.3'
}
/** For frontend build */
node { // (3) - nodeの設定
version = '4.4.0'
download = true
workDir = file("${projectDir}/src/main/web")
nodeModulesDir = file("${projectDir}/src/main/web")
}
grunt {
workDir = file("${projectDir}/src/main/web")
}
grunt_build.dependsOn 'installGrunt' // (4) - gruntの実行環境を構築する
grunt_build.dependsOn 'npmInstall'
processResources.dependsOn grunt_build
/** Other settings */
task wrapper(type: Wrapper) {
gradleVersion = '2.12'
}
(1) npm/grunt を操作する Gradle プラグインを指定する
com.moowork.gruntプラグインでは Grunt をインストールするinstallGrunt
タスクと、Grunt のタスクを実行するgrunt_<task>
ルールが Gradle タスクとして追加される。
今回はgrunt_build
タスクの実行により grunt のbuild
タスクを実行することを想定している。
また、com.moowork.nodeプラグインで、node.js/npm のインストールと node/npm タスクの実行を Gradle から行うことができる。
(2) フロントエンドビルドの成果物を Java ビルドのリソースに組み込む
今回、フロントエンド系の資産についてはsrc/main/web
内で扱うようにしていますが、そこで生成された js/css ファイルを Java ビルドの成果物に含めるためにsrc/main/web/bower_components
を resources フォルダとして追加しています。
(3) node の設定
com.moowork.node
で使用する設定をnode
節に、com.moowork.grunt
で使用する設定をgrunt
節にそれぞれ記述しています。
先にも述べたように、node/grunt で扱う資産はsrc/main/web
内で扱うため、workDir にそのディレクトリを指定しています。
(4) grunt の実行環境を構築する
grunt_build タスクが実行される前にinstallGrunt
,npmInstall
が実行されるよう設定しています。
また、grunt_build
タスクが、Java ビルドのリソースを処理するprocessResources
タスクの実行前に実行されるよう設定しています。
ビルドスクリプト(Grunt)
Gradle のgrunt_build
タスクにより実行される grunt ファイル.
src/main/web/Gruntfile.js
に置いてある.
module.exports = function(grunt) {
grunt.initConfig({
"bower-install-simple": { // (1) - bower installを実行する
options: {
directory: "bower_components/public" // (2) - bower installの出力先を変更する
},
"prod": {
options: {
production: true
}
},
"dev": {
options: {
production: false
}
}
},
wiredep: {
task: {
src: ['../resources/templates/**/*.jade'], // (3) - jadeファイルにbowerコンポーネントへの参照をinjectする
directory: 'bower_components/public',
ignorePath: '../../web/bower_components/public' // (4) - bowerコンポーネントへの参照パスから要らない部分を削除する
}
}
});
grunt.loadNpmTasks('grunt-bower-install-simple');
grunt.loadNpmTasks('grunt-wiredep');
grunt.registerTask('default', 'build');
grunt.registerTask('build', ['bower-install-simple', 'wiredep']);
};
(1) bower install を実行する
bower install
を実行する.
今回、bower.json にはbootstrap
への依存のみ宣言してある.
(2) bower install の出力先を変更する
bower install
の出力先がbower_components
ままだと、bower のコンポーネントを Spark で Static ファイルとして配信出来ない(ココらへんは Gradle のprocessResource
タスクとの関係).
とりあえず今回はbower install
の出力先を変更し、Spark の Static ファイルを/public
に指定することにしている.
(3) jade ファイルへ bower コンポーネントへの参照を inject する
bower install
で install されたコンポーネントへの参照を jade ファイルへ記述するタスク.
便利なのだが、grunt-wiredep
はもうメンテされてないらしい. stephenplusplus/grunt-wiredep: Inject Bower packages into your source code with Grunt.
(4) bower コンポーネントへの参照パスから要らない部分を削除する
(3) jadeファイルへbowerコンポーネントへの参照をinjectする
で jade ファイルに inject されるパスは、相対パスのため../../web/bower_components/public/bootstrap/dist/css/bootstrap.css
というような形式になる.
このままでは Web サーバとして動作させた時に上手く動かないため、不要な部分を削る.
この削り方は(2) bower installの出力先を変更する
に依存しており、ここらへんの操作のコンテキストがとても高くなりがちなのが悩みどころ.
アプリケーションコード
package org.kaakaa.spark;
import spark.ModelAndView;
import spark.template.jade.JadeTemplateEngine;
import java.util.Collections;
import static spark.Spark.get;
import static spark.Spark.port;
import static spark.Spark.staticFileLocation;
public class Main {
public static void main(String[] args) {
port(8080);
staticFileLocation("/public"); // (1) - Staticファイルの格納場所として`/public`を指定する
get("/hello", (rq, rs) -> new ModelAndView(Collections.EMPTY_MAP, "hello"), new JadeTemplateEngine());
}
}
(1) Static ファイルの格納場所として/public
を指定する
ビルドスクリプト(Grunt)の(2) bower installの出力先を変更する
でも触れたように、Spark の Static ファイルの格納場所として/public
を指定する.
アプリケーションコード(jade)
doctype html
html(lang="ja")
head
title gradle-frontend-boilerplate
// bower:css // (1) - grunt-wiredepタスクによりbowerのmainコンポーネントがinjectされる
// endbower
// bower:js
// endbower
body
nav.navbar.navbar-inverse.navbar-fixed-top
.container
.navbar-header
a.navbar-brand Gradle-frontend-boilerplate
.container(style="margin-top: 50px;")
h1 gradle-frontend-boilerplate
p Hello World!
(1) grunt-wiredep タスクにより bower の main コンポーネントが inject される
grunt のgrunt-wiredep
タスクが実行されると、bower の main コンポーネントとして指定されている js/css ファイルへの参照がコメントの間に inject される。