Kotlin/JS TIPS - JavaScript ライブラリへの型情報の付与
概要
JavaScript ライブラリを使用する際に、型情報を付与する方法について説明します。 前回の記事で JavaScript ライブラリの使用方法を説明しましたが、 静的型付けによる恩恵を得ずに JavaScript の柔軟性を重視した使用方法としていました。 型情報を付与することによって、以下の恩恵を得られる様にしていきます。
ライブラリ開発者の手間を増やして、ライブラリ利用者の手間を減らしていきます。
前回の記事:
目次
確認環境
- IntelliJ IDEA Community 2017.2
- Kotlin 1.1.3-2
- Gradle 3.5
- Groovy 1.4.10
参考情報
- kotlin.js - Kotlin Programming Language
- Kotlin/JS 標準ライブラリ
- Calling JavaScript from Kotlin - Kotlin Programming Language
external interfaceの使い方
解説
json 関数と Json インターフェース
JavaScript では {} によってオブジェクト生成を行う。
Kotlin/JS では json 関数によってオブジェクトを生成できる。
生成されたオブジェクトは Json インターフェース を実装したオブジェクトであり、
[] による操作を行える。
以下は SampleClient.kt の一部である。
fun run() { val option = json() option["el"] = "#example" option["data"] = json("message" to "Hello World") }
生成される JavaScript は以下の様になっている。
function run() { var option = json([]); option['el'] = '#example'; option['data'] = json([to('message', 'Hello World')]); }
json 関数のソースコード (Kotlin) は以下になっており、js("({})") でオブジェクトを生成していることがわかる。
public fun json(vararg pairs: Pair<String, Any?>): Json { val res: dynamic = js("({})") for ((name, value) in pairs) { res[name] = value } return res }
また、Json インターフェースのソースコード (Kotlin) は以下になっている。
public external interface Json { operator fun get(propertyName: String): Any? operator fun set(propertyName: String, value: Any?): Unit }
external を使うことで JavaScript のコードをそのまま使用することができる。
get/set を定義することで [] による参照/代入を可能にしている。
型情報の付与
json 関数の仕組みを応用して以下のとおりに記述することで、Json コンストラクタに見せかけることができる。
fun Json(): Json = js("({})") fun run() { val option = Json() option["el"] = "#example" }
さらに、external を使うことで JavaScript のコードをそのまま使用できる性質を利用して、
[] を使わずにオブジェクトに値を設定できる様にする。
例えば、以下の様に。
fun <T : Json> Json(): T = js("({})") external interface VueOption : Json { var el: Any var data: Json } external interface Model : Json { var message: String } fun run() { val option = Json<VueOption>() option.el = "#example" option.data = Json<Model>().apply { message = "Hello World" } option["computed"] = Json() // VueOption は Json インターフェースを実装しているので [] を使える }
生成された JavaScript は以下のとおり。
external を使用した部分は簡潔なコードとなっている。
コード上に VueOption や Model インターフェースは現れない。
function Json() { return {}; } function run() { var option = Json(); option.el = '#example'; var $receiver = Json(); $receiver.message = 'Hello World'; option.data = $receiver; option['computed'] = Json(); }
さらに、初期化処理を行う関数を引数 (init) にとる Json 関数を追加することで、
() や apply を省略できる。
fun <T : Json> Json(init: T.() -> Unit): T = Json<T>().apply(init) fun run() { val option = Json<VueOption> { el = "#example" data = Json<Model> { message = "Hello World" } this["computed"] = Json() }
external interface を使うことで型情報を付与し、
補完機能の恩恵を得えつつ、
誤った型の値を代入する危険性を減らすことができる。
Vue.js に適用する
さらに、Vue 関数を SampleClient.kt に追加すると、Vue の初期化が簡潔になる。
@JsNonModule @JsModule("vue") external class Vue(option: VueOption) fun <T : Json> Json(): T = js("({})") fun <T : Json> Json(init: T.() -> Unit): T = Json<T>().apply(init) external interface VueOption : Json { var el: Any var data: Json } external interface Model : Json { var message: String } fun Vue(init: VueOption.() -> Unit): Vue = Vue(Json<VueOption>().apply(init)) fun run() { val vm: dynamic = Vue { el = "#example" data = Json<Model> { message = "Hello World" } } vm.message = "Hello Kotlin World" }
前回の記事と比べると随分と簡潔になったと思う。
しかし、検討すべき課題は残っている。
補完機能の恩恵は受けられるようになったが、型安全という面では十分とは言えない。
上記の例で言えば、el は String もしくは HTMLElement であるため、Any にしている。
また、vm.message を参照するために dynamic にしている。
JavaScript の動的な側面を扱う方法については検討が必要である。
ソースコード
Kotlin/JS で JavaScript ライブラリを使用する
概要
Kotlin/JS から JavaScript ライブラリを使用する方法を説明します。 AMD に対応している JavaScript ライブラリを前提としています。 例として Lodash, Vue.js を使います。 作成するコードでは型付けは弱いままとして、JavaScript の柔軟性を活かした例としています。 Kotlin による静的型付けの恩恵は得られません。 静的型付けを活かした書き方は別途記事を書きます。
前回の記事の続きになっているので、必要に応じて参照してください。
ソースコード : Release 2017-07-30-211651 · nosix/kotlin-js · GitHub
目次
確認環境
- IntelliJ IDEA Community 2017.2
- Kotlin 1.1.3-2
- Gradle 3.5
- Groovy 1.4.10
参考情報
- RequireJS API
- RequireJS 公式
- Calling JavaScript from Kotlin - Kotlin Programming Language
- Kotlin から JavaScript を呼ぶ方法
解説
JavaScript ライブラリを使用する
前回の記事では、 RequireJS を使用して JavaScript の依存関係を管理できるようにした。 RequireJS は AMD に対応している JavaScript ライブラリを管理するだけでなく、AMD に対応していないライブラリも管理できる。
本記事では、AMD に対応していないライブラリについては記載しない。
AMD に対応していないライブラリでは試行していないためである。
RequireJS にて AMD に対応していないライブラリを使用する場合には config で shim を使えば良いらしい。
(参照 : RequireJS使い方メモ - Qiita)
以降では、AMD に対応しているライブラリについて説明する。
JavaScript ライブラリを CDN 経由で使用する
ライブラリをダウンロードせず、CDN (Content Delivery Network) を使用して手軽に試したい場合がある。
例えば、Lodash を使う場合、以下のサイトで配信されており、URL が記載されている。
lodash free CDN links by jsDelivr - A super-fast CDN for developers and webmasters
RequireJS で使用するには、前回の記事で作成した require-config.js に上記サイトに記載されている URL を追記すればよい。
以下は require-config.js の内容である。
var require = { baseUrl: 'build/js', // 追加 paths: { lodash: 'https://cdn.jsdelivr.net/lodash/4.17.4/lodash', // 拡張子の.jsは不要 }, // ^^^ enforceDefine: true, };
次に、Kotlin/JS のコードを記述する。 型チェックが弱いままで使用する方法である。 JavaScript のコードをほぼそのまま記述するため記述の手間は少ないが、型チェックや補完機能の恩恵を得られない方法である。
SampleClient.kt を以下に変更する。
external val lodash: dynamic fun run() { println(lodash.capitalize("hello world")) }
external val lodash: dynamic とすると、JavaScript の lodash 変数を参照できる。
external は外部、すなわち JavaScript のコードを示す。
dynamic は変数の型が動的であることを示す。
ビルドで生成される JavaScript のコードは以下になる。(一部抜粋)
function run() { println(lodash.capitalize('hello world')); }
このコードでは lodash 変数が見つからずにエラーとなる。
また、生成された sample-client.js では lodash への依存関係が定義されない。
エラーを解決するために SampleClient.kt を以下に変更する。
@JsNonModule @JsModule("lodash") external val lodash: dynamic fun run() { println(lodash.capitalize("hello world")) }
依存関係を定義させるためには @JsModule を使用して、モジュール名(ライブラリ名)を指定する。
@JsModule を使用しており、かつ build.gradle で moduleKind = "plain" か moduleKind = "umd" とした場合には、ビルドエラーとなる。
モジュールシステムを使用しない場合のために @JsNonModule を指定する必要がある。
前回の記事に引き続き moduleKind = "umd" としているため、
@JsModule と @JsNonModule を指定している。
@JsModule を指定した場合には、生成される JavaScript コードは以下になる。(一部抜粋)
function run() { println($module$lodash.capitalize('hello world')); }
以上でエラーは解消され、ブラウザのコンソールに Hello world が表示される。
Lodash を使う場合の注意点
Lodash を moduleKind = "plain" で使う場合には注意が必要である。
Lodash や Underscore.js では Global スコープの _ を使用する。
そのため、SampleClient.kt のコードは以下にする。
external val `_`: dynamic
fun run() {
println(`_`.capitalize("hello world"))
}
Kotlin/JS が生成するコードでは、Closure スコープに _ を使用する。
このため、Global スコープの _ を隠蔽してしまいエラーとなる。
sample-client.js を読み込む前に _ を lodash に代入して、変数名も lodash にすれば対応できる。
しかし、モジュールシステムを使用した方が簡潔である。
JavaScript ライブラリをダウンロードして使用する
Gradle では JavaScript ライブラリとの依存関係定義のために WebJars を使用することができる。 前回の記事でも RequireJS との依存関係定義に使用した。
例として、Vue.js の依存関係を追加する。
sample-client/build.gradle に以下を追加する。
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
compile "com.example:sample:1.0-SNAPSHOT"
compile 'org.webjars:requirejs:2.3.3'
compile 'org.webjars:vue:2.1.3' // 追加
}
次に、Vue.js の使い方に従い sample-client/index.html に以下を追記する。
<div id="example">{{ message }}</div>
message を表示するように SampleClient.kt を変更する。
@JsNonModule @JsModule("vue") external class Vue(option: Json) fun run() { val vm = Vue(json( "el" to "#example", "data" to json( "message" to "Hello World" ) )) }
比較のため JavaScript で記述する場合を記載しておく。
var vm = new Vue({ el: '#example', data: { message: 'Hello World' } })
Kotlin と JavaScript の対応は以下になっている。
| Kotlin | JavaScript |
|---|---|
Vue() |
new Vue() |
json() |
{} |
"key" to value |
key: value |
Lodash の時との違いとしては、external val Vue: dynamic ではなく external class Vue(option: Json) としている。
Vue.js の場合にはモジュールオブジェクトは関数オブジェクトになっており、その関数はインスタンス生成に使用される。
val Vue とした上で Vue(...) とすると関数実行になるが、
class Vue とした上で Vue(...) とすればインスタンス生成になる。
client-sample/index.html をブラウザで表示すれば、Hello World と表示される。
しかし、vm 変数の型は dynamic ではなく Vue となる。
これにより、Vue クラスに定義されていないプロパティを参照できなくなる。
例えば、Vue.js では JavaScript の場合には以下のように書くことができる。
var vm = new Vue({ el: '#example', data: { message: 'Hello World' } }) vm.message = 'Hello Kotlin World'
Kotlin/JS では class Vue にプロパティを定義しなければならない。
しかし、以下の様に dynamic 型を使えばプロパティに定義しなくとも参照できる。
fun run() { val vm: dynamic = Vue(json( "el" to "#example", "data" to json( "message" to "Hello World" ) )) vm.message = "Hello Kotlin World" }
ソースコード
Gradle で Kotlin/JS ライブラリを開発する
概要
Gradle を使用して Kotlin/JS の開発環境を構築し、Hello World を作成します。 更に、ライブラリとして配布可能にする方法、そのライブラリを使用する方法を説明します。 JavaScript のライブラリを使用する方法は別の記事で説明します。
なお、筆者は JavaScript 界については詳しくないので、誤りがあれば優しく教えてください。
目次
確認環境
- IntelliJ IDEA Community 2017.2
- Kotlin 1.1.3-2
- Gradle 3.5
- Groovy 2.4.10
参考情報
- Kotlin to JavaScript - Kotlin Programming Language
- 公式チュートリアル
- Dynamic Type - Kotlin Programming Language
- 公式リファレンス
- kotlin.js - Kotlin Programming Language
- 公式API
解説
環境構築
Gradle の kotlin2js plugin を使用して Kotlin/JS のコードをビルドする環境を構築する。
モジュール単位に js ファイルが生成されるため、複数のモジュールを作成する前提で構築する。
手順としては、空プロジェクトを作成し、モジュールを追加する。
モジュール名は sample として作成した。

初期設定
生成された sample/build.gradle は以下の通り。
group 'com.example' version '1.0-SNAPSHOT' buildscript { ext.kotlin_version = '1.1.3-2' repositories { mavenCentral() } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } apply plugin: 'kotlin2js' repositories { mavenCentral() } dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version" }
Sample.kt を src/main/kotlin に作成する。
fun main(args: Array<String>) { println("Hello World") }
module root (例では sample ディレクトリ) にてビルドを実行。
$ ./gradlew build
HTML (sample/index.html) を作成する。HTML ではビルドにより生成された JavaScript を読み込む。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>サンプル</title> </head> <body> <script src="build/classes/main/sample_main.js"></script> </body> </html>
ここまでに作成、および生成したファイルは以下の通り。(重要な部分のみ記載。)
sample <- module root
build
classes
main
sample_main
root-package.kjsm
sample_main.js
sample_main.meta.js
src
main
java
kotlin
Sample.kt
resources
test
java
kotlin
resources
build.gradle
index.html
ブラウザで表示すると kotlin モジュールが見つからずにエラーとなる。
kotlin.js の抽出
Gradle を使う場合には jar ファイルから js ファイルを抽出する必要がある。
sample/build.gradle に以下を追記する。
(参照 : http://kotlinlang.org/docs/tutorials/javascript/getting-started-gradle/getting-started-with-gradle.html)
build.doLast() {
// Copy *.js from jar into build directory
configurations.compile.each { File file ->
copy {
includeEmptyDirs = false
from zipTree(file.absolutePath)
into 'build/classes/main'
include '**/*.js'
eachFile { details ->
def names = details.path.split('/')
details.path = names.getAt(names.length - 1)
}
}
}
}
HTML では kotlin.js を読み込ませる記述を sample_main.js の前に追記。
<script src="build/classes/main/kotlin.js"></script> <script src="build/classes/main/sample_main.js"></script>
以上でブラウザのコンソールに Hello World が表示される。
パスの変更
ソースコードを配置するディレクトリと生成される JavaScript が配置されるディレクトリは階層が深い。 階層を減らすことで何らかの支障をきたすかもしれないが、 今回のサンプルの範囲では問題ないため、 配置するディレクトリを変更する。 (今回のサンプルでは、ソースセットには Kotlin ソースコード以外は置かず、テストを作成していないため問題ない。)
sample/build.gradle に以下を追記する。
sourceSets {
main.kotlin.srcDirs += "src/main"
test.kotlin.srcDirs += "src/test"
}
def outputDir = "${projectDir}/build/js"
compileKotlin2Js {
kotlinOptions {
outputFile = "${outputDir}/sample.js"
}
}
build.doLast() {
// Copy *.js from jar into build directory
configurations.compile.each { File file ->
copy {
includeEmptyDirs = false
from zipTree(file.absolutePath)
into outputDir // 変更
include '**/*.js'
eachFile { details ->
def names = details.path.split('/')
details.path = names[names.length - 1]
}
}
}
}
ファイルの配置は以下に変更される。
sample <- module root
build
js
sample
root-package.kjsm
kotlin.js
kotlin.meta.js
sample.js
sample.meta.js
src
main
Sample.kt
test
build.gradle
index.html
JavaScript の配置が変更されたので HTML (sample/index.html) の内容も変更する。
<script src="build/js/kotlin.js"></script> <script src="build/js/sample.js"></script>
生成ファイル
ビルドすることで生成されるファイルは 3 種類ある。
*.js- モジュールの JavaScript ファイル
*.meta.js- Kotlin to JavaScript - Kotlin Programming Language
- リフレクションやその他の機能に使用されるメタファイル
- Kotlin to JavaScript - Kotlin Programming Language
*.kjsm- javascript - Kotlin: What is a kjsm file? - Stack Overflow
- Kotlin JavaScript Meta ファイル
- IDE で型チェックをするときに使用
- javascript - Kotlin: What is a kjsm file? - Stack Overflow
ライブラリ化
ビルドすることで jar ファイルが生成される。 jar ファイルには 3 種類のファイル (js, meta.js, kjsm) が全て含まれている。 kjsm ファイルが含まれていれば、IDE の型チェックが有効となる。
例えば、sample モジュールの main 関数を sayHello 関数に変更して、他のモジュール (sample-client モジュールを作成) から使用する。
まず、sample モジュールの Sample.kt を以下に変更する。
fun sayHello() { println("Hello World") }
ライブラリ化する際には、依存している js ファイルを抽出する必要はない。
sample/build.gradle から以下の記述を無効化、もしくは削除する。
//build.doLast() { // // Copy *.js from jar into build directory // configurations.compile.each { File file -> // copy { // includeEmptyDirs = false // // from zipTree(file.absolutePath) // into outputDir // include '**/*.js' // eachFile { details -> // def names = details.path.split('/') // details.path = names[names.length - 1] // } // } // } //}
ビルドすると build/libs/sample-1.0-SNAPSHOT.jar が生成される。
次に、sample-client モジュールを作成し、sample モジュールと同様に設定する。
jar の依存関係を追加すると sample-client/build.gradle は以下の通りになる。
buildscript {
ext.kotlin_version = '1.1.3-2'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin2js'
repositories {
mavenCentral()
// 追加
flatDir {
dirs '../sample/build/libs'
}
// ^^^
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
compile "com.example:sample:1.0-SNAPSHOT" // 追加
//compile 'com.example:sample:1.0-SNAPSHOT' // error!
}
sourceSets {
main.kotlin.srcDirs += "src/main"
test.kotlin.srcDirs += "src/test"
}
def outputDir = "${projectDir}/build/js"
compileKotlin2Js {
kotlinOptions {
outputFile = "${outputDir}/sample-client.js" // 変更
}
}
build.doLast() {
// Copy *.js from jar into build directory
configurations.compile.each { File file ->
copy {
includeEmptyDirs = false
from zipTree(file.absolutePath)
into outputDir
include '**/*.js'
eachFile { details ->
def names = details.path.split('/')
details.path = names[names.length - 1]
}
}
}
}
SampleClient.kt を以下の内容で作成する。
build/libs/sample-1.0-SNAPSHOT.jar に含まれる kjsm ファイルのおかげで補完機能が有効になっている。
fun main(args: Array<String>) {
sayHello()
}
HTML ファイル (sample-client/index.html) を作成して JavaScript ファイルを読み込む。
sample-client.js の前に kotlin.js と sample.js を読み込む必要がある。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>サンプル</title> </head> <body> <script src="build/js/kotlin.js"></script> <script src="build/js/sample.js"></script> <script src="build/js/sample-client.js"></script> </body> </html>
ここまでに作成/生成したファイルを整理すると以下になる。
sample-client <- module root
build
js
sample-client
root-package.kjsm
kotlin.js
kotlin.meta.js
sample.js
sample.meta.js
sample-client.js
sample-client.meta.js
src
main
SampleClient.kt
test
build.gradle
index.html
sample-client/index.html をブラウザで開くと、ブラウザのコンソールに Hello World が表示される。
依存関係管理
HTML に script タグを並べることで各種のライブラリを使用できるが、記述順を気にする必要がある。
依存関係の管理を楽にするために、JavaScript のモジュールシステムを使用する。
Kotlin/JS が対応している JavaScript モジュールシステム (moduleKind) は以下の 4 つ。
(参照 : JavaScript Modules - Kotlin Programming Language)
- plain
- モジュールシステムを使わない
- amd
- Asynchronous Module Definition (AMD) に対応させる
- require.js ライブラリで使用する
- commonjs
- CommonJS に対応させる
- node.js/npm で使用する
- umd
ライブラリに JavaScript モジュールシステムを適用する
UMD を使用すればいずれのモジュールシステムにも対応できるので、UMD を指定してビルドする。
sample と sample-client の両方の build.gradle に moduleKind を追加する。
compileKotlin2Js {
kotlinOptions {
outputFile = "${outputDir}/sample-client.js"
moduleKind = "umd" // 追加
}
}
ビルドして生成された JavaScript ファイル (sample.js, sample-client.js) の冒頭には以下の様な記述が加えられる。
moduleKind = "plain" の場合 :
if (typeof kotlin === 'undefined') { throw new Error("Error loading module 'sample-client'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'sample-client'."); } if (typeof sample === 'undefined') { throw new Error("Error loading module 'sample-client'. Its dependency 'sample' was not found. Please, check whether 'sample' is loaded prior to 'sample-client'."); } this['sample-client'] = function (_, Kotlin, $module$sample) { ...snip... return _; }(typeof this['sample-client'] === 'undefined' ? {} : this['sample-client'], kotlin, sample);
Global オブジェクトに kotlin, sample, sample-client を登録している。
moduleKind = "umd" の場合 :
(function (root, factory) { if (typeof define === 'function' && define.amd) define(['exports', 'kotlin', 'sample'], factory); else if (typeof exports === 'object') factory(module.exports, require('kotlin'), require('sample')); else { if (typeof kotlin === 'undefined') { throw new Error("Error loading module 'sample-client'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'sample-client'."); } if (typeof sample === 'undefined') { throw new Error("Error loading module 'sample-client'. Its dependency 'sample' was not found. Please, check whether 'sample' is loaded prior to 'sample-client'."); } root['sample-client'] = factory(typeof this['sample-client'] === 'undefined' ? {} : this['sample-client'], kotlin, sample); } }(this, function (_, Kotlin, $module$sample) { ...snip... return _; }));
typeof define === 'function' && define.amd に該当すれば AMD、
typeof exports === 'object' に該当すれば CommonJS、
それ以外であればモジュールシステム無しと判断している。
JavaScript モジュールシステムを使い、読み込む
AMD を使用して依存関係を管理する。
AMD を使用するために require.js を導入する。
(CommonJS も少しだけ調べたけれどブラウザで使用するには複雑に見えたので、AMD だけを記載。)
sample-client/build.gradle に以下を追記する。
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
compile "com.example:sample:1.0-SNAPSHOT"
compile 'org.webjars:requirejs:2.3.3' // 追加
}
ビルドすることで build/js/require.js が展開される。
require.js を使うにあたって設定ファイル (require-config.js) を作成しておく。
(参照 : RequireJS API)
var require = { baseUrl: 'build/js', enforceDefine: true, };
設定の内容は以下のとおり。
build/js以下にある JavaScript ファイルを読み込む (baseUrl)- JavaScript の読み込みに失敗したときはエラーとする (
enforceDefine)
次に、require.js を使用するように HTML (sample-client/index.html) を変更する。
<script src="require-config.js"></script> <script src="build/js/require.js"></script> <script>require(['sample-client']);</script>
sample-client を指定することで、sample-client が依存している kotlin と sample が読み込まれる。
読み込み先は require-config.js で指定されている。
sample, sample-client のいずれでも依存関係は Gradle に集約できている点が嬉しい。
最後に、ブラウザで開くとブラウザのコンソールに Hello World が表示される。
おまけ
sample-client は読み込むだけで main 関数が実行されてしまう。
再利用性の観点からは望ましくない。
SampleClient.kt は以下に変更する。
fun run() {
sayHello()
}
sample-client/index.html は以下に変更する。
<script src="require-config.js"></script> <script src="build/js/require.js"></script> <script> require(['sample-client'], function(client) { client.run(); }); </script>
ソースコード
Google App Engine スタンダード環境で Kotlin + Spring Boot を動かす
概要
2017.6.28 に Google App Engine スタンダード環境が Java8 をサポートしたと告知されました。 告知記事を読んでいると Kotlin + Spring Boot で動かすサンプルが目にとまったので、動かしてみました。
確認環境
- macOS 10.12.5
- Google Cloud SDK 161.0.0
- 158.0.0 でインストール後にアップデート
- App Engine Java 1.9.54
- IntelliJ IDEA Community 2017.1.4
参考情報
- Google Cloud Platform Blog: Google App Engine standard now supports Java 8
- 告知記事
- getting-started-java/appengine-standard-java8/kotlin-springboot-appengine-standard at master · GoogleCloudPlatform/getting-started-java · GitHub
- Google App Engine + Kotlin + Spring Boot のサンプル
- Gradle と App Engine プラグインを使用する | Java の App Engine スタンダード環境 | Google Cloud Platform
- Gradle で環境構築するための方法
解説
Google Cloud Platform
- プロジェクトを作成
(設定手順をメモしていませんでした…。)
Google Cloud SDK
手順は以下の通り。
ダウンロード後、ファイルの SHA256 チェックサムを検証してからファイルを展開。
install.sh を実行する前に好みのディレクトリに配置する。
筆者はユーザーのホームディレクトリに置いている (/Users/user_name/google-cloud-sdk) 。
install.sh を実行することで、インストール済みコンポーネントを確認でき、コンポーネントのインストール/アンインストール方法を知れる。
また、環境変数 PATH の自動設定を行える。
$ echo "6572e086d94ab2a6b3242a966b688a382a80a417101315d59d38ac804b5a366e google-cloud-sdk-158.0.0-darwin-x86_64.tar.gz" | shasum -a 256 -c google-cloud-sdk-158.0.0-darwin-x86_64.tar.gz: OK $ tar xvfz google-cloud-sdk-158.0.0-darwin-x86_64.tar.gz $ cd google-cloud-sdk $ ./install.sh ... Do you want to help improve the Google Cloud SDK (Y/n)? ... Modify profile to update your $PATH and enable shell command completion? Do you want to continue (Y/n)? ... [/Users/user_name/.bash_profile]: ...
途中の Y/n は全て初期設定 (Enter) を選択。~/.bash_profile にて PATH の設定が追加される。
Terminal を起ち上げ直して、以下を続行。 Java Extension コンポーネントをインストールして、SDK をアップデートする。 その後にアカウントを初期化する。
$ gcloud components install app-engine-java $ gcloud components update $ gcloud init ... You must log in to continue. Would you like to log in (Y/n)? ... Please enter numeric choice or text value (must exactly match list item): google_cloud_platform_project_id ... API [compute-component.googleapis.com] not enabled on project [xxxxxxxxxxx]. Would you like to enable and retry? (Y/n)? ...
途中の Y/n は初期設定 (Enter) を選択。ブラウザが起動して認証を求められる。
SDK の設定諸々は以下のコマンドで確認できる。
$ gcloud info ... Installation Root: [/Users/user_name/google-cloud-sdk] ...
IntelliJ IDEA でプロジェクト作成
- Gradle Project を作成
- appengine-web.xml 作成
- Java8 スタンダード環境を指定
- build.gradle 作成
- Application.kt 作成
- JsonObjectMapper の設定
- TemplateEngine の設定
- JsonObjectMapper 作成
- Controller, Template などを作成 (省略)
- appengineRun タスクで実行
- ローカル環境で動かす
- appengineDeploy タスクで配置
- Google の環境で動かす
appengine-web.xml, build.gradle, Application.kt, JsonObjectMapper は以下の Gist を参照のこと。
Google App Engine Standard Environment in Java8 wi …
Serializer/Deserializer クラスは ObjectMapper の addSerializer/addDesirializer メソッドに型を合わせるために用意している。
IntelliJ IDEA の Terminal にて、以下を実行すればローカル環境で試せる。
$ ./gradlew appengineRun ... 04, 2017 4:56:51 午前 com.google.appengine.tools.development.AbstractModule startup 情報: Module instance default is running at http://localhost:8080/ 7 04, 2017 4:56:51 午前 com.google.appengine.tools.development.AbstractModule startup 情報: The admin console is running at http://localhost:8080/_ah/admin 7 04, 2017 1:56:51 午後 com.google.appengine.tools.development.DevAppServerImpl doStart 情報: Dev App Server is now running
http://localhost:8080/ をダブルクリックで選択し、コンテキストメニューの Open as URL を選択するとブラウザで開ける。
(もっと楽に起動できる方法は?)
同じく、以下を実行すれば Google Cloud Platform 環境に配置される。
$ ./gradlew appengineDeploy ...
配置後、gcloud app browser とすることでデフォルトブラウザが開くが、デフォルトブラウザの設定が不明。
OS のデフォルトは Chrome に設定されているのに、Firefox が開く。
(どこに設定が?)
他の設定もしていた build.gradle から余計な設定を削除したため、もしかしたら削除し過ぎていて問題が起こるかも。 IntelliJ IDEA に Google Cloud Tools plugin をインストールしているが、無くてもよいはずなので記載していない。 Google Cloud Tools を使うことで App Engine に配置できるはずが機能しない。 (何か設定しないとだめ?)
IntelliJ IDEA で Emacs 風キーマップを作る
概要
IntelliJ IDEA の keymap 設定を Emacs にしても、使いたい機能のショートカットが複雑で覚えられません。 そこで、Emacs 風のショートカットに設定を変更します。
筆者は最初に覚えたエディタが Emacs でしたが、その後は Emacs のライトユーザーです。 ヘビーユーザーからしたら Emacs 風ではないと思われるかもしれません。
確認環境
- IntelliJ IDEA Community 2017.1.4
参考情報
解説
Emacs と言えば、ESC キーと Ctrl キーから連なるショートカットが特徴的かと思います。
IntelliJ IDEA ではショートカットキーに Second stroke を設定することにより、
Emacs 風のショートカットキーを設定できます。
私は、Ctrl-[ を ESC キーとして使用しています(Emacs 標準のはず)。
そのため、Ctrl-[, Ctrl-x, Ctrl-c, Ctrl-h には Second stroke を設定しています。
Ctrl-x と Ctrl-c で使い分けがあったかもしれませんが無視することにして設定しています。
Ctrl-h はヘルプ系のアクションを割り当てています。
また、Emacs とは異なりますが、Navigation 系のアクションを Ctrl-j (Jump のつもり) に割り当てています。
基本的には、アクションの意味を表すようにキーを選択しているつもりです(Project ならば p, Intention なら i とか)。
使用頻度の低いと思われるアクションは Ctrl-[ x で Find Action を起動して実行する方針です。
設定した内容を整理したスプレッドシートを公開しています。 設定を変更していないアクションについては記載していません。
アクションの一覧は intellJ IDEA Community のソースコードから取得しています。
intellij-community/ActionsBundle.properties at master · JetBrains/intellij-community · GitHub
IntellJ IDEA の便利機能をまだまだ知らないので、こんな便利機能があるよ!という話があれば伺いたいです。 また、Emacs の標準的なショートカットキーで、このキー設定は鉄板!という話があれば伺いたいです。
libGDX で iOS アプリを起動できない
概要
libGDX で Multi-OS Engine を使用した iOS アプリを起動するとエラーが発生しました。 libGDX が初期設定で指定している Multi-OS Engine は 1.2.3 ですが 1.2.5 に変更する必要があります。
確認環境
- AndroidStudio 2.2.3
- libGDX 1.9.5
- Multi-OS Engine 1.2.3
- Xcode 8.2.1
参考情報
解説
Intel Multi-OS Engine 1.2.3 で実行すると以下のエラーが表示される。
Terminating app due to uncaught exception 'NSInvalidArgumentException'
build.gradle で moe-gradle のバージョンを 1.2.5 にすると解決する。
classpath 'org.multi-os-engine:moe-gradle:1.2.5'
libGDX 2D UI Library - Widgets
概要
libGDX の 2D UI Library のうち、Button 系以外の Widget についてまとめます。
目次
確認環境
- OS X El Capitan (10.11.6)
- Xcode 8.0
- Android Studio 2.2.2
- Multi-OS Engine Plugin 1.2.1
- Kotlin 1.0.4
- buildToolVersion 25.0.0
- compileSdkVersion 24
- libGDX 1.9.5-SNAPSHOT
参考情報
- Scene2d · libgdx/libgdx Wiki · GitHub
- Scene2d 公式文書。
- Viewports · libgdx/libgdx Wiki · GitHub
- Viewport 公式文書。
- Scene2d.ui · libgdx/libgdx Wiki · GitHub
- Scene2d UI 公式文書。
- libGDXの基礎5 Scene2Dを使う - Qiita
- 概要。
解説
Image
| 設定項目 | 型 | 説明 |
|---|---|---|
| drawable | Drawable | 描画対象 |
| Skin, String | ||
| NinePatch | ||
| Texture | ||
| TextureRegion | ||
| scaling | Scaling | Scaling.none, Scaling.fit, Scaling.fill, Scaling.stretch (default)。(fill, stretch には X, Y のみがある。) |
| align | Int | Align.topLeft, Align.top, Align.topRight, Align.right, Align.bottomRight, Align.bottom, Align.bottomLeft, Align.left, Align.center (default)。 |
サイズ
| min | pref | max | |
|---|---|---|---|
| width/height | 0 | drawable のサイズ | 0 |
Scaling (align = Align.center の場合)

| Scaling.none | Scaling.fit |
|---|---|
![]() |
![]() |
| Scaling.fill | Scaling.stretch |
![]() |
![]() |
サンプルコード
private val actor by lazy { Image(Texture("badlogic.jpg")).apply { width = this@MyGdxGame.stage.width height = this@MyGdxGame.stage.height setScaling(Scaling.none) //setScaling(Scaling.fit) //setScaling(Scaling.fill) //setScaling(Scaling.stretch) setAlign(Align.center) } } override fun create() { ... stage.addActor(actor) }
Label
| 設定項目 | 型 | 説明 |
|---|---|---|
| text | CharSequence | 描画対象。改行文字 ('\n') で改行される。 |
| style | Label.LabelStyle | background, font, fontColor |
| Skin | ||
| labelAlign | Int | Label 全体の配置。Align.topLeft, Align.top, Align.topRight, Align.right, Align.bottomRight, Align.bottom, Align.bottomLeft, Align.left (default), Align.center。 |
| lineAlign | Int | 各行の配置。Align.left (default), Align.center, Align.right。 |
| ellipsis | Boolean | true ならば width を超えないように末尾を "..." に置き換える。(default: false) |
| String | null 以外ならば width を超えないように末尾を指定した String に置き換える。 | |
| fontScaleX | Float | X 方向の拡大率。 |
| fontScaleY | Float | Y 方向の拡大率。 |
| wrap | Boolean | true ならば width を超える場合に改行する。ellipsis が優先される。(default: false) |
サイズ
| min | pref | max | |
|---|---|---|---|
| width/height | prefと同じ | text が収まるサイズを算出 | 0 |
text = "Hello World\nHello Beautiful World!!"、fontScaleX = 5、fontScaleY = 10 の場合の表示例。
| labelAlign = topLeft, lineAlign = center | labelAlign = center, lineAlign = center | labelAlign = bottomRight, lineAlign = center |
|---|---|---|
![]() |
![]() |
![]() |
| labelAlign = center, lineAlign = left | labelAlign = center, lineAlign = center | labelAlign = center, lineAlign = right |
|---|---|---|
![]() |
![]() |
![]() |
サンプルコード
private val actor by lazy { Label("Hello World\nHello Beautiful World!!", Label.LabelStyle(BitmapFont(), Color.WHITE)).apply { width = this@MyGdxGame.stage.width height = this@MyGdxGame.stage.height setFontScale(5f, 10f) setAlignment(Align.center, Align.center) } } override fun create() { ... stage.addActor(actor) }
TextField
| 設定項目 | 型 | 説明 |
|---|---|---|
| text | String | 入力欄に設定される文字列。改行文字 ('\n') は除去される。 |
| style | TextField.TextFieldStyle | BitmapFont (font, messageFont), Color (fontColor, messageFontColor, disabledFontColor, focusedFontColor), Drawable (background, disabledBackground, focusedBackground, cursor, selection) |
| Skin | ||
| alignment | Int | Align.left, Align.center, Align.right。 |
| maxLength | Int | 入力可能文字数。 |
| messageText | String | text 未入力時に表示する文字列。入力を削除しても再表示されない。 |
| onlyFontChars | Boolean | true (default) ならば、text に set する際に BitmapFont に含まれない文字を除去する。false ならば、含まれない文字を空白に置き換える。 |
| passwordMode | Boolean | true ならば、入力文字列を passwordCharacter で表示する。false (default) ならば、入力文字列をそのまま表示する。 |
| passwordCharacter | Char | passwordMode が true のときに使用する文字。設定しないと passwordMode = true でもマスクされない。 |
サイズ
| min | pref | max | |
|---|---|---|---|
| width/height | prefと同じ | ? | 0 |
サンプルコード
private val actor by lazy { TextField("", TextField.TextFieldStyle().apply { font = BitmapFont().apply { data.scale(10f) } messageFont = font fontColor = Color.WHITE messageFontColor = Color.RED }).apply { width = this@MyGdxGame.stage.width height = this@MyGdxGame.stage.height maxLength = 10 messageText = "message" isPasswordMode = false setPasswordCharacter('x') setAlignment(Align.right) setOnlyFontChars(false) //text = "aとbとc" } } override fun create() { ... stage.addActor(actor) }
TextArea
TextField と同様。 異なる点は text に改行文字を含むことができ、width を超える場合には改行して表示される。
List
| 設定項目 | 型 | 説明 |
|---|---|---|
| items | Array |
リストの項目。文字列として表示される。 |
| style | List.ListStyle | BitmapFont (font), Color (fontColorSelected, fontColorUnselected), Drawable (background, selection) |
| Skin |
サイズ
| min | pref | max | |
|---|---|---|---|
| width/height | prefと同じ | ? | 0 |
実行例

サンプルコード
private val actor by lazy { List<String>(List.ListStyle().apply { font = BitmapFont().apply { data.scale(10f) } fontColorUnselected = Color.WHITE fontColorSelected = Color.RED selection = SpriteDrawable(Sprite(Texture("badlogic.jpg"))) }).apply { width = this@MyGdxGame.stage.width height = this@MyGdxGame.stage.height setItems("pen", "apple", "pineapple") } } override fun create() { ... stage.addActor(actor) }
SelectBox
| 設定項目 | 型 | 説明 |
|---|---|---|
| items | Array |
リストの項目。文字列として表示される。 |
| style | SelectBox.SelectBoxStyle | BitmapFont (font), Color (fontColor, disabledFontColor), Drawable (background, backgroundOpen, backgroundOver, backgroundDisabled), List.ListStyle (BitmapFont (font), Color (fontColorSelected, fontColorUnselected), Drawable (background, selection)), ScrollPane.ScrollPaneStyle (Drawable (background, corner, hScroll, hScrollKnob, vScroll, vScrollKnob)) |
| Skin | ||
| maxListCount | Int | ドロップダウン時に表示する項目数。(default: items の要素数) |
| scrollingDisabled | Boolean | true ならば、ドロップダウン時のスクロールを無効にする (maxListCount の個数しか表示されない)。false (default) ならば、有効にする。 |
サイズ
| min | pref | max | |
|---|---|---|---|
| width/height | prefと同じ | ? | 0 |
実行例
| default | maxListCount = 3 | maxListCount = 3, scrollingDisabled = true |
|---|---|---|
![]() |
![]() |
![]() |
サンプルコード
private val actor by lazy { val commonFont = BitmapFont().apply { data.scale(10f) } SelectBox<String>(SelectBox.SelectBoxStyle().apply { font = commonFont fontColor = Color.WHITE background = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply { color = Color.YELLOW }) backgroundOpen = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply { color = Color.GRAY }) listStyle = List.ListStyle().apply { font = commonFont fontColor = Color.GRAY fontColorUnselected = Color.GREEN fontColorSelected = Color.RED selection = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply { color = Color.RED }) } scrollStyle = ScrollPane.ScrollPaneStyle().apply { vScroll = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply { color = Color.BLUE }) vScrollKnob = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply { color = Color.MAGENTA }) } }).apply { val stageW = this@MyGdxGame.stage.width val stageH = this@MyGdxGame.stage.height setPosition(stageW/2 - width/2, stageH - height) setItems("1", "2", "3", "4", "5", "6", "7", "8", "9", "0") //maxListCount = 3 //setScrollingDisabled(true) } } override fun create() { ... stage.addActor(actor) }
ProgressBar
| 設定項目 | 型 | 説明 |
|---|---|---|
| min | Float | 最小値。 |
| max | Float | 最大値。 |
| stepSize | Float | 最小値から最大値を分割したときの一区画のサイズ。value は近くの区画に丸められる。 |
| vertical | Boolean | true ならば、縦方向に表示。false ならば、横方向に表示。 |
| style | ProgressBar.ProgressBarStyle | Drawable (background, knob, knobBefore, knobAfter, disabledBackground, disabledKnob, disabledKnobBefore, disabledKnobAfter) |
| Skin | ||
| visualInterpolation | Interpolation | value に set して、表示が更新される際のアニメーション表示方法。 |
| animateDuration | Float | アニメーション時間。単位は秒。 |
| animateInterpolation | Interpolation | setAnimationDuration を実行した際に行われるアニメーションの表示方法。 |
サイズ
| min | pref | max | |
|---|---|---|---|
| width/height | prefと同じ | ? | 0 |
実行例
| vertical = false | vertical = true |
|---|---|
![]() |
![]() |
サンプルコード
private val actor by lazy { ProgressBar(0f, 100f, 25f, false, ProgressBar.ProgressBarStyle().apply { background = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply { color = Color.WHITE }) knob = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply { color = Color.BLUE }) knobBefore = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply { color = Color.RED }) knobAfter = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply { color = Color.GREEN }) } ).apply { width = this@MyGdxGame.stage.width height = this@MyGdxGame.stage.height setVisualInterpolation(Interpolation.fade) setAnimateDuration(10f) value = 60f setAnimateInterpolation(Interpolation.bounce) setAnimateDuration(20f) } } override fun create() { ... stage.addActor(actor) }
Slider
ProgressBar のサブクラス。
| 設定項目 | 型 | 説明 |
|---|---|---|
| min | Float | 最小値。 |
| max | Float | 最大値。 |
| stepSize | Float | 最小値から最大値を分割したときの一区画のサイズ。value は近くの区画に丸められる。 |
| vertical | Boolean | true ならば、縦方向に表示。false ならば、横方向に表示。 |
| style | Slider.SliderStyle | Drawable (background, knob, knobBefore, knobAfter, knobDown, knobOver, disabledBackground, disabledKnob, disabledKnobBefore, disabledKnobAfter) |
| Skin | ||
| visualInterpolationInverse | Interpolation | value に現在の value よりも小さい値を set して、表示が更新される際のアニメーション表示方法。 |
サイズ
| min | pref | max | |
|---|---|---|---|
| width/height | prefと同じ | ? | 0 |
サンプルコード
private val actor by lazy { Slider(0f, 100f, 25f, false, Slider.SliderStyle().apply { background = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply { color = Color.WHITE }) knob = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply { color = Color.BLUE }) knobBefore = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply { color = Color.RED }) knobAfter = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply { color = Color.GREEN }) knobDown = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply { color = Color.MAGENTA }) } ).apply { width = this@MyGdxGame.stage.width height = this@MyGdxGame.stage.height value = 60f setVisualInterpolationInverse(Interpolation.smooth) setAnimateDuration(3f) value = 10f } } override fun create() { ... stage.addActor(actor) }














