続・Java と TypeScript をタイプセーフにツナグ
前回のエントリの続きといった感じです。
自分は、今のところ Webアプリは、サーバサイド Java、クライアントサイド TypeScript で作りたいと思っています。
実際 TypeScript を利用していて、タイプセーフになってないと思う部分がありました。
サーバからの Jsonのレスポンスをany
で受け取り、それをそのまま何かしらのテンプレートエンジンに流し込んでたわけですが、それって全然タイプセーフじゃないなと。
で、前回、Java の Entity から TypeScript のクラスを自動生成すれば、ある程度タイプセーフになるんじゃないかと思って、その処理を作ってみたわけです。
今回、ちょっと修正して Gradle のタスクとして呼び出すようにしてみました。
クライアントサイドも Virtual DOM 系のフレームワークの方が、タイプセーフ的になるかもと思い、Mithril.js で Example を作ってみました。
以下の様な構成です。
- サーバサイド: Spring Boot (Java)
- ビルド: Gradle
- クライアントサイド: Mithril.js(TypeScriptで)
例によって単純な足し算・引き算をする Example です。
サーバサイド
package app; import io.github.chibat.j2ts.annotation.TypeScriptClass; import lombok.Data; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @EnableAutoConfiguration public class CalculateController { @RequestMapping("/calc") Result calculate(Input input) { // (1) Result result = new Result(); result.add = input.arg1 + input.arg2; result.subtract = input.arg1 - input.arg2; return result; } @TypeScriptClass // (2) @Data public static class Input { private Integer arg1; private Integer arg2; } @TypeScriptClass // (2) public static class Result { public Integer add; public Integer subtract; } public static void main(String[] args) throws Exception { SpringApplication.run(CalculateController.class, args); } }
(1)の calculate メソッドでリクエストパラメータを2つ受け取り、足し算・引き算して結果を Json でレスポンスしています。 バリデーション的なものは省略しています。
で、(2)で TypeScriptClass アノテーションをクラスに付けることにより、TypeScript クラスのコードの出力対象としています。
ビルドスクリプト(Gradle)
repositories.jcenter() : : 色々省略 : dependencies { compile 'org.springframework.boot:spring-boot-starter-web:1.2.5.RELEASE' compile 'org.webjars:mithril:0.2.0' compile 'org.projectlombok:lombok:1.16.4' compile 'io.github.chibat:j2ts-annotation:0.0.0' // (1) } task generateTypeScriptCode(type: JavaExec) { configurations{j2ts} dependencies {j2ts 'io.github.chibat:j2ts-generator:0.0.0'} // (2) classpath = sourceSets.main.runtimeClasspath classpath += configurations.j2ts main = 'io.github.chibat.j2ts.generator.Main' args( 'app', // (3) 'src/main/typescript/entity.ts' // (4) ); }
(1) TypeScriptClass アノテーションを使用するために、依存を指定しています。
(2) TypeScriptのコードを出力するジェネレータの依存を指定しています。
(3) TypeScriptClass アノテーションが付いたクラスを探す際の対象となるベースパッケージ名を指定します。
(4) 出力する TypeScriptファイル名を指定します。
依存 jar の j2ts は、jcenter にアップしています。
次のコマンドを実行します。
$ gradlew generateTypeScriptCode
ファイル src/main/typescript/entity.ts が出力されます。
module app.CalculateController { export class Input { arg2: number; arg1: number; } export class Result { add: number; subtract: number; } }
クライアントサイド
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>j2ts - Example</title> </head> <body> <div id="calc-root"></div> <script src="/webjars/mithril/0.2.0/mithril.min.js"></script> <script src="/js/generated/entity.js"></script> <script src="/js/generated/calc.js"></script> </body> </html>
mithril.js は、JSで view を書くんで、HTMLファイル側は、<div id="calc-root"></div>
って書くくらいで、そこに view が展開される感じです。
次に、TypeScript のコードです。
/// <reference path="typings/mithril/mithril.d.ts" /> import Result = app.CalculateController.Result; import Input = app.CalculateController.Input; function esc(value: number | string) { return value || value === 0 ? value : ''; } class Controller { arg1 = m.prop<number>(null); arg2 = m.prop<number>(null); result = new Result(); request = () => { // (1) if (this.arg1() && this.arg2()) { var input = new Input(); input.arg1 = this.arg1(); input.arg2 = this.arg2(); m.request<Result>({method: 'GET', url: '/calc', data: input, dataType: 'json'}) .then((res)=>{this.result = res;}); } } } m.module(document.getElementById("calc-root"), { controller: () => { return new Controller(); }, view: (ctrl: Controller) => { // (2) return [ 'Arg1', m('input',{type: 'text', value: ctrl.arg1(), onchange: m.withAttr("value", ctrl.arg1)}), m('br'), 'Arg2', m('input', {type: 'text', value: ctrl.arg2(), onchange: m.withAttr("value", ctrl.arg2)}), m('br'), m('button', {onclick: ctrl.request}, 'Calc'), m('br'), 'Add: ' + esc(ctrl.result.add), m('br'), 'Subtract: ' + esc(ctrl.result.subtract), m('br'), ]; } });
(1)がサーバにリクエストを投げるところで、生成されたInput
クラスをリクエスト、Result
クラスをレスポンスとして利用しています。
(2)は、view の構築をしている訳ですが、タグじゃないんで見づらい感はありますが、きっと慣れれば大丈夫です。。 その代わり、タイプセーフ感は増すと思います。
また、Spring MVC だと RPC 的な TypeScript のコードも吐き出せそうだなーとは考えているところです。
Example コードは、github に上げています。 ジェネレータの j2ts は、 jcenter に登録しております。
以上