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.