メモ2ブログ

メモtoウェブログ。旧ブログはこちら。 http://sakebook.blogspot.jp/

Kotlin/JSことはじめ

なんでもKotlinで解決したいと思いがちの筆者です。

Kotlin/JSを触ってみたので動かし方などまとめます。

環境は

  • Kotlin 1.3.61
  • Node 13.3.0

です

Setup

Gradle Plugin

kotlin js プラグインを使います。

  • build.gradle.kts
plugins {
    kotlin("js") version "1.3.61"
}

kotlin jsプラグインを使えば、settings.gradleでのpluginManagementは不要です。

Kotlin関連のプラグインやライブラリでは kotliin("XXX") という書き方をすることで記述をスッキリできます。中身はただの拡張関数です

Dependencies

kotlin stdlib-js ライブラリを使います。

  • build.gradle.kts
dependencies {
    implementation(kotlin("stdlib-js"))
}

Build configuration

フロント(Browser)向けのJSと、サーバ(Node)向けのJSがあるので、どちらでも動かせるようなJSを吐き出すようにします。

targetブロックではビルドする対象を指定し、tasksブロックでは既存taskのプロパティの変更を行っています。

  • build.gradle.kts
kotlin.target {
    nodejs()
    browser()
}

tasks {
    compileKotlinJs {
        kotlinOptions {
            moduleKind = "umd"
        }
    }
}

moduleKindに設定できる値は amd, commonjs , umd , plain とありますが、デフォルトは plain です。

フロントで使いたければ amd, サーバで使いたければ commonjs, 両方で使いたければ umd を選択する感じです。

今回はどちらでも動かすので umdを選択します。

main.kt

src フォルダに作成します。 - main.kt

fun main() {
    println("Hello Kotlin/JS!!")
}

Build

次のコマンドを実行します

$ ./gradlew build

build フォルダが生成され、次のような構成になります。

.
└── build
    ├── js
    │   ├── node_modules/
    │   ├── node_modules.state
    │   ├── package.json
    │   ├── packages/
    │   ├── packages_imported/
    │   └── yarn.lock
    ├── kotlin
    │   ├── compileKotlinJs/
    │   └── sessions/
    ├── libs
    │   ├── kotlin-js-example-0.0.1.jar
    │   └── kotlin-js-example-js-0.0.1-sources.jar
    ├── reports
    │   └── tests/
    └── tmp
        ├── JsJar/
        ├── expandedArchives/
        └── kotlinSourcesJar/

jsは、JavaScriptのルートフォルダのようなものです。js/packages にトランスパイルされたJSファイルが含まれています。js/packages_importedにはトランスパイルされたJSファイルが依存しているpackageが含まれています。

Run

Browser

動作確認用に適当なhtmlファイルを作成します。

プロジェクトルートに次のhtmlを作成します。

  • index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <script src="build/js/packages_imported/kotlin/1.3.61/kotlin.js"></script>
    <script src="build/js/packages/kotlin-js-example/kotlin/kotlin-js-example.js"></script>
</head>

<body>
<p>Kotlin/JS example</p>
</body>
</html>

生成したJSファイルはkotlin.jsに依存しているので、kotlin.jsを読み込んだ後に生成した ${module}.jsを読み込みます。

ファイル名はプロジェクトのものが適応されます。

f:id:sakebook:20200218015239p:plain

ちなみに今回は詳しく取り上げませんが、 divコードから動的に生成できます。

Node

nodeコマンドで実行します。

$ node build/js/packages/kotlin-js-example/kotlin/kotlin-js-example.js
Hello Kotlin/JS!!

これでどちらでも動作確認ができました。

JavaScriptライブラリの追加

kotlinブロックの中で定義します。さらにどのsourceフォルダで利用するかも指定します。 このあたりはMPPが意識されてる感じがしますね。

次の例では dayjs を追加してます。

  • build.gradle.kts
kotlin {
    sourceSets["main"].dependencies {
        implementation(npm("dayjs", "^1.8.20"))
    }
}

npm 関数を使います。 これで build/js/node_modules/ 内にDLされます。

ライブラリの利用(Node)

ソースを次のように変更します。

  • main.kt
external fun require(module: String): dynamic

fun main() {
    val dayjs = require("dayjs")
    println("Hello Kotlin/JS!! ${dayjs()}")
}

これをnodeで実行します。

$ node build/js/packages/kotlin-js-example/kotlin/kotlin-js-example.js
Hello Kotlin/JS!! Sat, 08 Feb 2020 10:56:59 GMT

少し解説します。

external

external 修飾子を付けたものはJavaScriptからも呼び出しできるようにトランスパイルされます。そのままの名前で変換されます。

nodeで require が定義されているため今回は実行できています。これを別名にするとエラーになります。

  • main.kt
external fun req(module: String): dynamic

fun main() {
    val dayjs = req("dayjs")
    println("Hello Kotlin/JS!! ${dayjs()}")
}
$ node build/js/packages/kotlin-js-example/kotlin/kotlin-js-example.js
/USER_PATH/build/js/packages/kotlin-js-example/kotlin/kotlin-js-example.js:35
    var dayjs = req('dayjs');
                ^

ReferenceError: req is not defined

自由に命名したい場合JsNameアノテーションをつけてあげれば解決します。

  • main.kt
@JsName("require")
external fun req(module: String): dynamic

dynamic

JavaScriptの世界の型を受け止めるための便宜的な型です。

dynamicはどんな変数や関数も代入でき、実行可能です。

関数はdynamic型を返します。

未定義の関数は実行時にTypeErrorになります。未定義の変数はundefinedになります。

  • main.kt
fun main() {
    val dyn: dynamic = object{}
    println(dyn.some)
    dyn.foo = "foo"
    println(dyn.foo)
    dyn.bar = {
        println("bar")
    }
    dyn.bar()
    println(dyn.bar)
    dyn.some()
}
$ node build/js/packages/kotlin-js-example/kotlin/kotlin-js-example.js
undefined
foo
bar
function main$lambda() {
    println('bar');
    return Unit;
  }
/USER_PATH/build/js/packages/kotlin-js-example/kotlin/kotlin-js-example.js:35
    dyn.some();
        ^

TypeError: dyn.some is not a function

dynamic型の仕様についてはこちらにドキュメントがあります。

github.com

ライブラリの利用(Browser)

Browserの場合は require で読み込みは行わないのでエラーになります。

f:id:sakebook:20200218021013p:plain

Browserの場合は、 require で読み込むのをやめて追加したライブラリをscriptタグで読み込んであげると実行できます。

  • main.kt
fun main() {
    println("Hello Kotlin/JS!! ${js("dayjs()")}")
}
  • index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <script src="build/js/packages_imported/kotlin/1.3.61/kotlin.js"></script>
    <script src="build/js/node_modules/dayjs/dayjs.min.js"></script>
    <script src="build/js/packages/kotlin-js-example/kotlin/kotlin-js-example.js"></script>
</head>

<body>
<p>Kotlin/JS example</p>
</body>
</html>

f:id:sakebook:20200218021305p:plain

js

js 関数の引数に渡した文字列は、そのままJavaScriptのコードとして実行されます。

この方法だとBrowserとNodeのどちらでも動くJavaScriptが作成できません。なので、ライブラリをModule化します。

ライブラリのModule化

external修飾子をつけているものに、更にJSModuleアノテーションを付与します。

引数にはnpmなどでインストールする名前と同じものを使います。

umd 形式の場合、JSNonModuleアノテーションもセットで必要です。

  • main.kt
@JsModule("dayjs")
@JsNonModule
external fun dayjs(): dynamic

fun main() {
    println("Hello Kotlin/JS!! ${dayjs()}")
}

これで実行可能な形式でJSファイルが生成されているので、どちらの形式でも実行可能になりました。

実際のライブラリに則している形であれば定義方法は自由です。例えば次のように定義することもできます。

  • main.kt
@JsModule("dayjs")
@JsNonModule
@JsName("dayjs")
external class DayJs(any: Any? = definedExternally) {
    fun format(): String
}

fun main() {
    println("Hello Kotlin/JS!! ${DayJs().asDynamic()}")
    println("this year         ${DayJs().asDynamic().year()}")
    println("Valentine         ${DayJs("2020-02-14").asDynamic()}")
    println("Formatted date    ${DayJs().format()}")
}
$ node build/js/packages/kotlin-js-example/kotlin/kotlin-js-example.js
Hello Kotlin/JS!! Sat, 15 Feb 2020 10:47:09 GMT
this year         2020
Valentine         Thu, 13 Feb 2020 15:00:00 GMT
Formatted date    2020-02-15T19:47:09+09:00

definedExternallyプレースホルダのようなものです。JSで定義されていてKotlin側からはわからないものがあるときに使います。

yearは実際にdayjsライブラリで定義されている関数を、dynamicから直接呼び出しています。なので型はdynamicで返ってきます。

formatも実際にdayjsライブラリで定義されているの関数なのですが、再定義することで型情報を与えることができます。

Kotlinで気持ちよくJSのライブラリを利用するには、このようにモジュールを作成して必要な関数たちを再定義してあげる必要があります。

JetBrains公式からReactのラッパーモジュールが提供されています。

github.com

Webpack

先程buildコマンドで作成したBrowser用のJSファイルは、複数必要でした。これだと、ライブラリを追加するたびにhtmlも変更しなければならないため、手間です。

Webpackを利用してビルドすることで、1つのJSファイルにまとめることができます。

次のコマンドで作成できます。このコマンドはtargetブロックで browser を宣言していないと使えないので注意が必要です。

$ ./gradlew browserWebpack

作成されたJSファイルは build/distributions/ に吐き出されます。

htmlを変更します。

  • index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <script src="build/distributions/kotlin-js-example.js"></script>
</head>

<body>
<p>Kotlin/JS example</p>
</body>
</html>

読み込むのは1つのファイルですが、無事実行できました。

f:id:sakebook:20200218021707p:plain

このJSファイルはnodeからも実行できます。

$ node build/distributions/kotlin-js-example.js 
Hello Kotlin/JS!! Sat, 15 Feb 2020 11:36:33 GMT
this year         2020
Valentine         Thu, 13 Feb 2020 15:00:00 GMT
Formatted date    2020-02-15T20:36:33+09:00

browserRun というコマンドもあるのですが、まだバグがあり正常に終了しないのでJSファイルが作成できません。ちなみにbrowserRunで立ち上がるサーバは、 src/main/resrouces/index.html を見ます。

まとめ

今回はKotlin/JSを一から、 umd 形式でアウトプットして動かしてみました。

実際は commonjs 形式で利用することが多いと思いますが、このように、どうしてそうなるのかをきちんと理解しておけば、困ったときにデバッグしやすいと思います。

ネットで見つかるサンプルも、古いものや解説がないものばかりなのでこの記事は参考になると思います。

今回検証したリポジトリはこちらです。

github.com

参考

Kotlin to JavaScript - Kotlin Programming Language

https://youtrack.jetbrains.com/issue/KT-27679

GitHub - Kotlin/kotlinx.html: Kotlin DSL for HTML

GitHub - iamkun/dayjs: ⏰ Day.js 2KB immutable date library alternative to Moment.js with the same modern API

Calling JavaScript from Kotlin - Kotlin Programming Language

dartdocでAPIドキュメントを作成する

Dartで書かれたプロジェクトであれば、 dartdoc でドキュメントが自動生成できます。

$ dartdoc

自分が作りたかった理由はPluginのscoreを上げたかったからなのですが、ローカルで試すに当たり、Dart自体の、Pluginには不要なドキュメントが生成されてしまいました。

なのでそれを除外するコマンドが次です。

$ dartdoc --exclude 'dart:async,dart:collection,dart:convert,dart:core,dart:developer,dart:io,dart:isolate,dart:math,dart:typed_data,dart:ui,dart:ffi,dart:html,dart:js,dart:js_util'

まだ泥臭い指定をしなければ一緒に生成されてしまうみたいです。

確認したバージョンは次のとおりです。

$ dartdoc --version
dartdoc version: 0.29.1

今開発中のバージョンが 0.30.1まで出てますが、関連した修正は無さそうです。気軽に対象のディレクトリが指定できる形だと楽な気がしますね。

参考

dartdoc | Dart

dartdoc command should not generates docs for dart-sdk · Issue #1949 · dart-lang/dartdoc · GitHub

2019年を振り返って

目標とか

ジム通い

1月から通い始めていたのですが、目標週2ペースで行きたかったのですが、結果的には週1ペースでした。

体調崩したり旅行に行っていたことを考えると、継続できていたのかなと思います。

競プロ

AtCoderを取り組んでたのですが、継続できませんでした。正直数えるほどしかできてないのですが、毎回Dが解けなかったです。そこでアルゴリズムの勉強をしないと先に進めないなと思い、その段階で止まってしまいました。

OKR

個人OKRを立てて取り組めていた時期は調子が良かったです。しかし業務に忙殺されてから調子が狂い、リズムを取り戻せませんでした。

やったこと

気付ける仕組みとか会社でも使えるBotとか作った

リリースしてから一定期間が経過したら教えてくれるやつ

github.com

Slackのユーザグループから一人を選択してくれるBot

github.com

SlackからGoogle Calendarの予定をシュッと見れるBot

github.com

それぞれGitLabのPipeline schedulesで動かしたり、Cloud Functionsで動かしたり、Cloud Runで動かしたりして遊んでます。

Flutter

2019年はFlutterに触れました。

技術調査を兼ねてFlutter on the webを試してみたり、いくつかPluginを公開したりしました。

Flutterでネイティブ広告を表示するためのPlugin

pub.dev

Flutter/DartのPluginをシュッと公開するためのGitHub Actions

github.com

Contribute

大なり小なりはあるのですが、最近だとFirebase Admin Java SDK名前が乗ったのは嬉しかったです。

環境を作るのがちょっと面倒だった(Firebaseのプロジェクトが必要だったり)ので、あまりContributeされてないのかもしれません。

技術書典8

今年も参加しました。隔会で参加してます。

GoogleがFitbitを買収したのでこれから人気になるかもしれません。

sakebook.hatenablog.com

こちらからお求めになれます。

sakebook.booth.pm

正直なところ、現状Fitbitで利用している技術とGoogleのモバイル周りが全然マッチしていないのでどうするんだろうという感じです。

FitbitはJerryScriptをベースにしたFitbit OSで動かしているので、それを捨てるとなると現状作ってきたエコシステムを捨てることになるのであんまりそうはしないだろうなと思っているのですが(Versa 2を出したばっか)。なのでAndroid以外の何かを乗っけてくるのかもしれません。例えばFuchsiaとか乗っけてきたりして。

登壇

Flutter Meetup Tokyo で何回かLTをしました。

www.slideshare.net

www.slideshare.net

趣味とかその他

生活を豊かにすることにハマってきた

悪く言えば浪費なのですが、家電とか買いました。

生活が豊かになるのは楽しいです。家にいる時間が長い人ほど家に対してはお金をかけた方が良いです。

海外旅行

2週間ほどでタイとベトナムに行きました。

タイのバンコクは思った以上に栄えていました。一部は東京以上に都会で、一部は田舎以上に廃れてて差が激しかったです。昨日行った場所で爆発があったりして若干ヒヤリとした場面もありました。

f:id:sakebook:20200105184125p:plain

https://lh3.googleusercontent.com/JjRXOKsX26kfcRNXasW6QM9PI8MXiw2Zga37GZnkHyoqOqU09d-hIP0WJyXoiVVPc9h1jUVSusR7TDvk7v25xzG6sZoz6r6ktOIEBinjuNqwxB8oGUfCWXSVaM3LqgnS8XjVYNlIbd5y8OVm8MHiKW3OaPhaUMuiRauxGp9nhGOwXoTf8MtFwWG5UkhoPPMm1ocYfSHw6_UNrRQcGb1PhApEhnDPU3-Y8x8CDJzfRex8GevHiWW1DUwicBb92OF8rHW3U9nDcs54Q3UGIBmJgEJqm-fwpp5boI3HNnlzEIxwuXjrUoLk4DiCp95a6lYRlUnxpyPObQIH4q9b6kX-FyYlVIGl2pWJuJ0-gCxaLGc8mTVWktbpD37EzDO6Fhz0fpfJTZ47nBw3rV6xA4uxBmv3j2x8KAzSzY2c9xBZhWh-Pu834jsp7oScSeatG5V4JFcktyKBw1qgrdMHDB_u1AsbR7mTkpzeP2tM5NnVHEq3Jff3-GQfRbvKXqJD1qA_6Qy1exblIk8sdAlUtt6aI6eAcoqvYtP9LISamG-8e3qAi9dFReRTd4wHwgTdkLf2S5nVfrWS4khO2EV0Ef1vYYwkkHgf4OikQvnIXXBDFtgTuNmfp7TAgo0Z129mHkuDbXD8iw31-OULMXVF1EXUomQL0EnkvJPEWB0kE1j_LSwSnXRW_iB8Phoi=w1770-h1327-no

ベトナムハノイを拠点にしたのですが、バンゾックの滝(徳天瀑布)を見に行くために片道12時間の移動をしたのは良い経験でした。また、現地のツアー会社で申し込んでハロン湾のクルージングにも行きました。全部英語だったので苦戦しました。

https://lh3.googleusercontent.com/Qa7BLkB0jfKioXIu4-uSZKkzm9Jo0ugu3dV3zxeNZk3qmwd0Nx8CbIefQ07SfnRyDqHgHgr_gC5-CUnrMEdGz1nNaOhKl9T8TMpzAQ6O1StZUMwMJcZ19M7wSidcPU43wSAJ9SvkuBsU7puRs8vc6LqYdBXDObLIpuf0BB8Qi0pJvjzqELoMRtFBKAjtoLikDps5E3QsPL9QRBdnVb0rEWhZ5CcgWvpdiRAG9ek92Xph7GQgW5WP3zXLzx2YpZig-KYLYzYDg8Gh9CeAZCQWJVcSgv0wGtv1c_z7uW-Al4EzgbsFtTRZ97ahcL9PjlxtP1JuFqewF8GuYmo4SIgR_Pbg6ysJzOf_7OCF55OhKZHFZT1_jGwnPDJ0l1XiadKAiKDt0PsRPYs3uCx2QWJDm84qcJm7qZ3GKgv77d5M1Q79E2ROMgMZ9CfwttqO0Mna0xz_5VZWutdFh6xhz4WNxsVAJpMUOWBWl5_D3yw_4C5LSqUyxUvCLa00QaxX8M5HDwnQhD7yXzJNAR8OYRnJnk1gxyPSk8QGMem6eRcRQbND9uHdQTqg1UdjKgnF0LPIwMVAnTIkzIyma7f5psrnWJTq_q2CIIm8mlqd_3aXm1gOfPDB0yceyzphmyXOx8lCMRkcTlt2fQ9zOkIefsBqbrp832JlHvE8c_iWqVj6TNkn9pVU-13unOLY=w1770-h1327-no

https://lh3.googleusercontent.com/stmkSl42bMgAgtjb_DlNF3uEvHq0XG0T446imLH-X5z1hiODiG_WM1fOd0VO1781L9bzDFZ0UJyCNEV-9mz6byyWcnXk9j2UoEKqPgTOLXnDEVZyKPK_0Iqjtlq0nVOeCiY4wOr5PFXtrDagGbLVOs8auUqE34OFe22Pm06EmhO7gGPQWxVpk2kWsuBF_6BCiLm9TZZEurllIeF9UzyNBGTeZRbG_wu-7QGjp8uYU1b5lI2qTHyETCZtIa8ynAN7RRD85YPMlq579Oj9yEYus6JJYw6EBM0vtHPn-Czu4mBWZQjdB9SzVo8vxavkQ5S7DRMs6gfR6n3_vk66w8BG-GT-5lDhxs4LMQKpkfoGR8SIE5p-UWY91lEvP-6ymmrhQLj_F4f7zMYRt500xF6gVHLNE4eE8D0A2q14_iYqSRKESYacBPZOiPwqQjBJ-SxP1f8P9NIlrwBDu0dZovRmn1QqjeOb8wPO6JGZYqqjqDEVj5pyn-QtkEpMhJEeyVQ1sXAE13dlpm6HV5JPUc3sqcBpOZGkK-7V2Y2QLurBYUsE1WUL_TOC_6dxoKEkDPYgnTHsoYJe6B4jJWQrOrL1Ak8kw6dtOTmzeIu-ceIa01rv7_4NRL16VB1yQL6Uh0QARBB-vApFvSbcXkaFCG3_u-YFQyxqk139FAlofvUjaJbHa2Z7Rdg-UnbZ=w1770-h1327-no

ボドゲ

今年も色々買いました。遊びたい人いたら言ってください。

  • Welcome To
  • Welcome To拡張
  • パッチワーク
  • スコットランドヤード東京
  • 横濱紳商伝
  • カタン航海者版
  • はぁって言うゲーム
  • 遙かなる喜望峰
  • 翡翠の商人
  • BOXWALK
  • ソレニア
  • イト
  • マジックメイズ
  • ディセプション
  • デクリプト
  • アルゴ
  • 花火
  • モダンアート

そろそろ自分でも作りたい。

体調崩した

虚血性腸炎というものに2回なりました。腸は昔から強くはないのですが、今後はもっと気を使った何かをしたほうがいいかもしれません。

2020年

英語

OSSのissueのやりとりとか、英語のドキュメントを読むとか、海外旅行とかで英語の必要性が自分の中で再び高まってきました。

何かをしたいときにその障害になることが英語であるのならば、解決できる障害なので今のうちにやっつけてしまおうと決意しました。

英語やるだけだとふわっとしてるので、英語でのアウトプットが何かしら出せるとこまでいけたらなと思います。

リングフィットアドベンチャー

来月から通勤ルートからジムがなくなってしまうので、ジムに行きにくくなります。その代わりにリングフィットアドベンチャーを年末からはじめました。

今の所継続できており、適度に筋肉痛も発生しているので、ジムから乗り換えようと思ってます。

Flutter

もっとFlutterのアウトプットをしていきたいし、コミュニティにも関わっていきたいと思ってます。

自分はWebの知識が弱いのですが、そのあたりも補えるあたりFlutterには嬉しさがあります。