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を読み込みます。
ファイル名はプロジェクトのものが適応されます。
ちなみに今回は詳しく取り上げませんが、 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型の仕様についてはこちらにドキュメントがあります。
ライブラリの利用(Browser)
Browserの場合は require
で読み込みは行わないのでエラーになります。
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>
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のラッパーモジュールが提供されています。
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つのファイルですが、無事実行できました。
この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
形式で利用することが多いと思いますが、このように、どうしてそうなるのかをきちんと理解しておけば、困ったときにデバッグしやすいと思います。
ネットで見つかるサンプルも、古いものや解説がないものばかりなのでこの記事は参考になると思います。
今回検証したリポジトリはこちらです。
参考
Kotlin to JavaScript - Kotlin Programming Language
https://youtrack.jetbrains.com/issue/KT-27679
GitHub - Kotlin/kotlinx.html: Kotlin DSL for HTML
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 command should not generates docs for dart-sdk · Issue #1949 · dart-lang/dartdoc · GitHub
2019年を振り返って
目標とか
ジム通い
1月から通い始めていたのですが、目標週2ペースで行きたかったのですが、結果的には週1ペースでした。
体調崩したり旅行に行っていたことを考えると、継続できていたのかなと思います。
競プロ
AtCoderを取り組んでたのですが、継続できませんでした。正直数えるほどしかできてないのですが、毎回Dが解けなかったです。そこでアルゴリズムの勉強をしないと先に進めないなと思い、その段階で止まってしまいました。
OKR
個人OKRを立てて取り組めていた時期は調子が良かったです。しかし業務に忙殺されてから調子が狂い、リズムを取り戻せませんでした。
やったこと
気付ける仕組みとか会社でも使えるBotとか作った
リリースしてから一定期間が経過したら教えてくれるやつ
Slackのユーザグループから一人を選択してくれるBot
SlackからGoogle Calendarの予定をシュッと見れるBot
それぞれGitLabのPipeline schedulesで動かしたり、Cloud Functionsで動かしたり、Cloud Runで動かしたりして遊んでます。
Flutter
2019年はFlutterに触れました。
技術調査を兼ねてFlutter on the webを試してみたり、いくつかPluginを公開したりしました。
Flutterでネイティブ広告を表示するためのPlugin
Flutter/DartのPluginをシュッと公開するためのGitHub Actions
Contribute
大なり小なりはあるのですが、最近だとFirebase Admin Java SDKに名前が乗ったのは嬉しかったです。
環境を作るのがちょっと面倒だった(Firebaseのプロジェクトが必要だったり)ので、あまりContributeされてないのかもしれません。
技術書典8
今年も参加しました。隔会で参加してます。
GoogleがFitbitを買収したのでこれから人気になるかもしれません。
こちらからお求めになれます。
正直なところ、現状Fitbitで利用している技術とGoogleのモバイル周りが全然マッチしていないのでどうするんだろうという感じです。
FitbitはJerryScriptをベースにしたFitbit OSで動かしているので、それを捨てるとなると現状作ってきたエコシステムを捨てることになるのであんまりそうはしないだろうなと思っているのですが(Versa 2を出したばっか)。なのでAndroid以外の何かを乗っけてくるのかもしれません。例えばFuchsiaとか乗っけてきたりして。
登壇
Flutter Meetup Tokyo で何回かLTをしました。
www.slideshare.net
www.slideshare.net
趣味とかその他
生活を豊かにすることにハマってきた
悪く言えば浪費なのですが、家電とか買いました。
- ドラム式乾燥洗濯機
- コードレス掃除機
- スマートロック
- スマートホームディスプレイ
- スマートライト
- 電動コーヒーミル
- アメリカンプレス
- Nintendo Switch
生活が豊かになるのは楽しいです。家にいる時間が長い人ほど家に対してはお金をかけた方が良いです。
海外旅行
2週間ほどでタイとベトナムに行きました。
タイのバンコクは思った以上に栄えていました。一部は東京以上に都会で、一部は田舎以上に廃れてて差が激しかったです。昨日行った場所で爆発があったりして若干ヒヤリとした場面もありました。
ベトナムはハノイを拠点にしたのですが、バンゾックの滝(徳天瀑布)を見に行くために片道12時間の移動をしたのは良い経験でした。また、現地のツアー会社で申し込んでハロン湾のクルージングにも行きました。全部英語だったので苦戦しました。
ボドゲ
今年も色々買いました。遊びたい人いたら言ってください。
- Welcome To
- Welcome To拡張
- パッチワーク
- スコットランドヤード東京
- 横濱紳商伝
- カタン航海者版
- はぁって言うゲーム
- 遙かなる喜望峰
- 翡翠の商人
- BOXWALK
- ソレニア
- イト
- マジックメイズ
- ディセプション
- デクリプト
- アルゴ
- 花火
- モダンアート
そろそろ自分でも作りたい。
体調崩した
虚血性腸炎というものに2回なりました。腸は昔から強くはないのですが、今後はもっと気を使った何かをしたほうがいいかもしれません。
2020年
英語
OSSのissueのやりとりとか、英語のドキュメントを読むとか、海外旅行とかで英語の必要性が自分の中で再び高まってきました。
何かをしたいときにその障害になることが英語であるのならば、解決できる障害なので今のうちにやっつけてしまおうと決意しました。
英語やるだけだとふわっとしてるので、英語でのアウトプットが何かしら出せるとこまでいけたらなと思います。
リングフィットアドベンチャー
来月から通勤ルートからジムがなくなってしまうので、ジムに行きにくくなります。その代わりにリングフィットアドベンチャーを年末からはじめました。
今の所継続できており、適度に筋肉痛も発生しているので、ジムから乗り換えようと思ってます。
Flutter
もっとFlutterのアウトプットをしていきたいし、コミュニティにも関わっていきたいと思ってます。
自分はWebの知識が弱いのですが、そのあたりも補えるあたりFlutterには嬉しさがあります。