Kotlin und Javascript
Ich spiele nun schon seit einer Weile mit Kotlin herum und weil ich zur Zeit ein kleines Webprojekt mache, kam ich auf die Idee den Javascriptteil mit KotlinJS abzudecken. Manche mögen vielleicht auch Javascript direkt schreiben, aber mir ist das irgendwie ein Graus, daher hab ich gerne irgendetwas anderes, was in Javascript übersetzt wird, bisher hatte ich da nur GWT. Also warum nicht mal KotlinJS ausprobieren?
Gradle Setup
Zunächst sah das Setup für Gradle mehr als einfach aus. Wäre es dabei geblieben, hätte ich hier auch nichts erwähnt. Also einfach nur um Javascript zu erzeugen reicht es das klassische Kotlin Setup folgendermaßen abzuändern:
apply plugin: 'kotlin2js'
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
}
Aber damit noch nicht genug - zumindest für mein Projekt. Damit werden dann im Ordner build/classes/kotlin/main
meine Javascript Dateien angelegt, aber man braucht auch die kotlin.js
auf der
Website, damit alles läuft. Klar, die hätte ich mir laden und so auf den Webspace packen können, jedoch wollte ich das nicht immer machen - beziehungsweise vergessen - wenn ich Kotlin update. Auf der
Seite von Kotlin ist glücklicherweise beschrieben, wie man das mit einem weiteren Task
ändert:
task assembleWeb(type: Sync) {
configurations.compile.each { File file ->
from(zipTree(file.absolutePath), {
includeEmptyDirs = false
include { fileTreeElement ->
def path = fileTreeElement.path
path.endsWith(".js") && (path.startsWith("META-INF/resources/") ||
!path.startsWith("META-INF/"))
}
})
}
from compileKotlin2Js.destinationDir
into "${buildDir}/web"
dependsOn classes
}
assemble.dependsOn assembleWeb
Was blöderweise nicht so gut funktioniert: Die unterschiedlichen Modultypen am Ende der Seite. Wenn ich das versuche wird beim ersten Durchlauf mein Modul gebaut und erst beim zweiten build
die
kotlin.js
und das fand ich echt unpraktisch. Also bin ich beim Standard geblieben, das luppt.
Einbinden in die Website
Das man die Javascript Dateien laden muss ist ja selbstredend. Aber man kann nicht einfach eine Funktion in Kotlin schreiben und sie im Javascript nutzen. Vielleicht ist das vollkommen normal für Javascriptmodule, da kenn ich mich gar nicht aus, aber ich hab einen Moment gebraucht um das zu verstehen.
Das Modul wird als Child in das window
Objekt der Website gepackt. Zunächst hatte ich es client-js
genannt, was sich als unpraktisch herausstellte. Ich musste immer mit window["client-js]"
darauf zugreifen. Als ich es dann in clientJs
umbenannt hab, konnte ich direkt mit clientJs
auf die darin enthaltenen Funktionen zugreifen. Auch werden alle Klassen und Packages von Kotlin
übernommen, sodass ich dann auf die Klasse AnimeAPI
im Package de.comhix.anime
mit clientJs.de.comhix.anime.AnimeAPI
zugreifen musste. Zudem kam noch ein .prototype
dran, um an das
companion object
zu gelangen.
Später habe ich es ein wenig abgeändert. Ich habe eine Datei “Main.kt” ohne Package angelegt und darin einfach val AnimeAPI = AnimeAPI()
reingepackt. So konnte ich dann aus meinem Javascript mit
einfach nur clientJs.AnimeAPI
auf die Funktionen der API zugreifen.
Im Grunde ist es wahrscheinlich auch schöner, wenn ich in meinem Websitecode überhaupt nicht auf das Javascript zugreife und nur in meinem Modul alle onClick
und ähnliches setze, aber das kommt
später irgendwann, so weit bin ich noch nicht.
Javascript in Kotlin schreiben
Wie bereits erwähnt hatte ich schon mit GWT zu tun. Soetwas ähnliches habe ich ursprünglich bei KotlinJS auch erwartet - weit daneben. Wenn man Javascript in Kotlin schreibt hat man quasi die normale Javascript API und die Sprachfeatures von Kotlin, anstatt die eingeschränkte Java API, die dann in Javascript übersetzt wird. Das sorgt auch für einen kleinen Nachteil: Man kann nicht wie bei einem Kotlin JVM Projekt irgendwelche Java Libraries mit reinnehmen. Reiner Kotlin Code an sich lässt sich aber meines wissens einbinden. Wenn man also eine Library hat, die Reactive Unterstützung bereit stellt, ohne auf Java zurückzugreifen, dann kann man die wohl nutzen.
Was ist mit dir nicht richtig?
Während der Entwicklung hatte ein paar echt seltsame Fehler. Ich wollte mir eine einfache Fassade für die fetch
API bauen.
Leider hat man da echt wenig zu gefunden, Tutorials für die Handhabung habe ich keine gesehen und letztendlich hab ich Code irgendwo in Gitlab gefunden, mit dem ich was hinbekommen habe.
class ApiCall(private val url: String,
private val method: Method = Method.GET) {
private val headers: Headers = Headers()
fun call(): Promise<Response> {
return window.fetch(url,
object : RequestInit {
override var method: String? = ApiCall::method.name
override var headers = ApiCall::headers
})
}
}
Das ist der grobe Aufbau. Hat auch alles compiled, aber ich bekam dann beispielsweise diesen Fehler TypeError: Failed to execute 'fetch' on 'Window': Invalid value
. Das Problem war der Zugriff auf
die Attribute von ApiCall
. Daher musste ich die Methode ein wenig überarbeiten:
fun call(): Promise<Response> {
val callMethod = method.name
val callHeaders = headers
return window.fetch(url,
object : RequestInit {
override var method: String? = callMethod
override var headers = callHeaders
})
}
Übrigens war es auch wichtig mit dem RequestInit
als object Expression, weil es sonst mit null-Werten vor die Wand gefahren wurde.
Namen sind doch nicht so wichtig
Außer man möchte von anderem Javascript auf Dinge zugreifen. Wurzel allen übels waren Generics und Lambdas. In dem Fall entstehen so lustige Namen wie UserAPI.prototype.login_qz9155$
. So kommt man
natürlich von externem Javascript nicht dran. Aber dafür gibt es eine ganz simple Lösung: Man schreibt @JsName("login")
an die Methode und alles ist gut.
Fazit
Im Grunde finde ich es echt angenehm zum arbeiten. Im Gegensatz zu GWT muss man sich keine Gedanken machen, ob Klasse XY oder Funktion ABC nutzbar sind. Alles was man schreiben kann funktioniert - abgesehen eventuell von kleinen Bugs. Die tollen Sprachfeatures kann man nutzen und man hat die Typensicherheit, die mir schon immer bei Javascript gefehlt hat. Was jedoch echt ein Problem darstellt ist der Mangel an Tutorials. In vielen Fällen konnte ich nichts oder nur mit sehr viel Aufwand (Google Seite 2!!) Lösungen oder Hinweise für meine Probleme finden. Ich hoffe es wird in Zukunft mehr genutzt, dass sich dieser Punkt bessert.
Alles in allem ist es aber interessant und angenehm zu entwickeln.