9

The last time I used Kotlin was Dec 2015 when I used it to solve a couple of Project Euler problems.

This time I want to try its interoperability with Javascript. Now my question is, how do we import/use existing Javascript libraries in Kotlin? I've seen some people using the native keyword, and I just want a brief explanation of it.

  • 1
    Places that might be good starting points for reading: https://discuss.kotlinlang.org/c/javascript, Kotlin Slack #javascript channel, and this other SO question http://stackoverflow.com/questions/36250680/writing-javascript-applications-with-kotlin – Jayson Minard Sep 20 '16 at 09:45

3 Answers3

7

There's no native keyword anymore, there's @native annotation. Currently, it's working solution and you can use it with 1.0.x branch of Kotlin compiler. However, we are going do deprecate this annotation in favour of extern annotations, so be prepared to rewrite your code eventually for 1.1.x branch.

When you put @native annotation on a class or on a top-level function, two things happen:

  1. Its body is not compiled to JavaScript.
  2. Compiler references this class or function directly, without package name and mangling.

I think it's easier to explain by providing example of a JavaScript library:

function A(x) {
    this.x = x;
    this.y = 0;
}
A.prototype.foo = function(z) {
    return this.x + this.y + z;
}

function min(a, b) {
    return a < b ? a : b;
}

and a corresponding Kotlin declaration

@native class A(val x: Int) {
    var y: Int = noImpl

    fun foo(z: Int): Int = noImpl
}

@native fun min(a: Int, b: Int): Int = noImpl

Note that noImpl is a special placeholder that's required because of non-abstract functions required bodies and non-abstract properties require initializers. BTW, when we replace @native with extern, we'll get rid of this noImpl.

Another aspect of interoperation with JS libraries is including libraries via module system. Sorry, we don't have any solution right now (but are going to release it soon). See proposal. You can use the following workaround for node.js/CommonJS:

@native interface ExternalModule {
    fun foo(x: Int)
}

@native fun require(name: String): dynamic = noImpl

fun main(args: Array<String>) {
   val module: ExternalModule = require("externalModule")
   module.foo(123)
}

where external module is declared like this

function foo(x) {
    return x + 1;
}
module.exports = { foo : foo };
Alexey Andreev
  • 1,980
  • 2
  • 17
  • 29
2

I added a simple barebone project as an example of how to do Kotlin2Js.

https://bitbucket.org/mantis78/gradle4kotlin2js/src

Here is the gradle file that is the main recipe.

group 'org.boonhighendtech'
version '1.0-SNAPSHOT'

buildscript {
    ext.kotlin_version = '1.1.2-5'
    repositories {
        maven { url 'http://dl.bintray.com/kotlin/kotlin-dev/' }
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

apply plugin: 'kotlin2js'

repositories {
    maven { url 'http://dl.bintray.com/kotlin/kotlin-dev/' }
    mavenCentral()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
}

build {
    outputs.dir("web/")
}

build.doLast {
    copy {
        from 'src/main/webapp'
        into 'web/'
        include '**/*.html'
        include '**/*.js'
        include '**/*.jpg'
        include '**/*.png'
    }

    configurations.compile.each { File file ->
        copy {
            includeEmptyDirs = false

            from zipTree(file.absolutePath)
            into "${projectDir}/web"
            include { fileTreeElement ->
                def path = fileTreeElement.path
                path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/"))
            }
        }
    }
}

clean.doLast {
    file(new File(projectDir, "/web")).deleteDir()
}

compileKotlin2Js {
    kotlinOptions.outputFile = "${projectDir}/web/output.js"
    kotlinOptions.moduleKind = "amd"
    kotlinOptions.sourceMap = true
}

Firstly, you can assign a dynamic variable then essentially code it like you code JavaScript, dynamically.

e.g.

val jQuery: dynamic = passedInJQueryRef
jQuery.whateverFunc()

But if your intention is to have it typed, then you need to introduce types to the external library. One way is to make use of the relatively extensive libraries of typedefs by https://github.com/DefinitelyTyped/DefinitelyTyped

Find the ts.d there, then run ts2kt (https://github.com/Kotlin/ts2kt) to get your Kotlin files. That typically gets you there. Occasionally, certain conversions are not well done. You will have to hand fix the conversion. E.g. snapsvg's snapsvg.attr() call takes in "{}" but it got converted to some strange interface.

It was

fun attr(params: `ts$2`): Snap.Element

And I replaced it with

 fun attr(params: Json): Snap.Element

and it works like a charm.

Boon
  • 1,871
  • 1
  • 21
  • 31
1

Kotlin 1.1 introduces the externalmodifier that can be used to declare functions and classes written directly in JS, see http://kotlinlang.org/docs/reference/js-interop.html

Peter T.
  • 2,927
  • 5
  • 33
  • 40