SpringBoot + TypeScriptをGradleでビルドする

Spring Boot使ったアプリをGradleでビルドしたらtsファイルもコンパイルしてbowerで入れたライブラリとかもwebpackでまとめてjsファイル吐いてくれたら素敵だなと思ってやってみた。

[ソースはここ](https://github.com/jun-1/spring-boot-with-typescript)

ディレクトリ構成はこんな感じ

```
src/
  └main/
    ├java/
    ├ts/ ・・・ここのtsファイルをコンパイルして
    └resources/
      └static/
      └templates/
  └test/
    ├java/
    └resources/
build/
  └classes/
    ├main/
    └test/
  └resources/
    └main/
      └static/
        └js/ ・・・ここに出力する
      └templates/
    └test/
```

# どうやるか
gradle-gulp-pluginというのがあったのでこれを使ってgulpのタスクとしてwebpackを動かそうと思う。さらにgradle-node-pluginを使うとnodeのインストールとか面倒見てくれるようなので環境構築手順書とか書く手間省けてよさげです。

# いろいろインストール
最終的にはnodeとかnpmとか入ってな環境でも`gradle build`ですべて完結するようになるけど、設定にファイルの生成などに必要な最低限のモジュールをグローバルにインストール。


```bash
$ npm install -g bower tsd
```

## プロジェクトの初期化
```bash
$ npm init
```

## プロジェクトに必要なツールをインストール
```bash
$ npm install --save-dev typescript webpack ts-loader gulp webpack-stream gulp-exec bower
```

## bowerでvueをインストール
```bash
$ bower init
$ bower install vue --save
```

## tsdでvueの定義ファイルを作成
TypeScriptから使うライブラリの型情報を追加します。

```bash
$ tsd init
$ tsd install vue --save
```

ここまでで以下のようなファイル構成になります

```
├bower_components/
├node_modules/
├src/
├typings/
├bower.json
├package.json
└tsd.json
```

# スクリプトの作成

VueのViewモデルを書いてみます。
[ここ](http://kuroeveryday.blogspot.jp/2015/05/TypeScriptWithVuejs.html)を参考にしました。


```ts:src/main/ts/app.ts
import Vue = require('vue');

class App extends Vue {
    constructor(){
        super(false);
        this._init({
            el: '#app',
            data: {
                message: 'Hello world'
            }
        });
    }
}
export var app = new App();
```

#TypeScriptのコンパイル設定

filesで`typings/tsd.d.ts`を指定してライブラリの型情報を使えるようにします。


```json:tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es5",
    "sourceMap": true
  },
  "files": [
    "src/main/ts/app.ts",
    "typings/tsd.d.ts"
  ]
}
```

# webpackの設定
ts-loaderを使ってコンパイルしたapp.tsとbowerで入れたvueをbundle.jsにまとめます。

```js:webpack.config.js
var webpack = require('webpack');
var path = require('path');

module.exports = {
  entry: './src/main/ts/app.ts',
  output: {
    path: __dirname + '/build/resources/main/static/js',
    filename: 'bundle.js'
  },
  devtool: 'inline-source-map',
  resolve: {
    root: [path.join(__dirname, "bower_components")],
    extensions: ['', '.ts']
  },
  plugins: [
    new webpack.ResolverPlugin(
      new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin("bower.json", ["main"])
    ),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ],
  module: {
    loaders: [
      { test: /\.tsx?$/, loader: 'ts-loader' }
    ]
  }
};
```

# gulpの設定
gulpにwebpackを実行するタスクを作成します。
buildを実行すると`bower install`を事前に実行してから`webpack`を実行します

```js:gulpfile.js
var gulp = require('gulp');
var webpack = require('webpack-stream');
var webpackConfig = require('./webpack.config.js');
var exec = require('gulp-exec');

gulp.task('install_bower', function(callback){
    return gulp.src('')
            .pipe(exec("./node_modules/bower/bin/bower install"));
});

gulp.task('build', ['install_bower'], function(callback){
  return gulp.src('./src/main/ts/app.ts')
    .pipe(webpack(webpackConfig))
    .pipe(gulp.dest('./build/resources/main/static/js/'));
});
```

# gradleの設定

classesタスク実行前に`npm install` `gulp build`を実行するように設定します。
また、nodeをダウンロードするようにします。

```groovy:build.gradle
buildscript {
  repositories {
    mavenCentral()
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "org.springframework.boot:spring-boot-gradle-plugin:1.2.6.RELEASE"
    classpath "com.moowork.gradle:gradle-node-plugin:0.11"
    classpath "com.moowork.gradle:gradle-gulp-plugin:0.11"
  }
}

apply plugin: "java"
apply plugin: "spring-boot"
apply plugin: "com.moowork.node"
apply plugin: "com.moowork.gulp"

node {
  version = '0.12.7'
  download = true
}

classes.dependsOn gulp_build
gulp_build.dependsOn npm_install 

repositories {
  mavenCentral()
}

dependencies {
  compile "org.springframework.boot:spring-boot-starter-web"
}
```

# 実行

```bash
$ gradle build
```

ちゃんと出来てます。

```bash
$ ls build/resources/main/static/js/          
bundle.js
```

動くか確認

```bash
$ gradle bootRun
```
![exec.png](https://qiita-image-store.s3.amazonaws.com/0/46832/f0c439bd-e5a2-d4c4-4338-12b7f3fd8bfa.png)

できました。

# nodeがインストールされてない環境でビルドできるか確認

こんなDockerfileを作ります。
rootで`bower install`するとエラーになるので別のユーザーを作ってます。

```:Dockerfile
FROM java:8

RUN useradd docker
RUN mkdir /home/docker
RUN chown docker /home/docker
USER docker
WORKDIR /home/docker
RUN git clone https://github.com/jun-1/spring-boot-with-typescript.git

WORKDIR spring-boot-with-typescript
RUN ./gradlew build

EXPOSE 8080
ENTRYPOINT ["java", "-jar", "./build/libs/spring-boot-with-typescript.jar"]
```

ビルド

```bash
$ docker build -t spring-boot-typescript .
```

起動

```bash
$ docker run -p 80:8080 spring-boot-typescript
```

nodeのない環境でもビルドして起動できました。[^1]

![exec2.png](https://qiita-image-store.s3.amazonaws.com/0/46832/c9f67ae3-080a-b526-3f0a-587311ce5d28.png)

[^1]: 192.168.99.100はdockerのホストVMのアドレスです