Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use Javascript libraries in Kotlin

Tags:

kotlin

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.

like image 310
Francis Fredrick Valero Avatar asked Sep 20 '16 08:09

Francis Fredrick Valero


People also ask

Can Kotlin use JavaScript library?

You can freely talk to JavaScript from Kotlin via dynamic types. If you want to use the full power of the Kotlin type system, you can create external declarations for JavaScript libraries which will be understood by the Kotlin compiler and the surrounding tooling.

Is Kotlin faster than Nodejs?

In general, Kotlin is 5-10x faster than JS and uses 1/2 the memory.

Does Kotlin use Java or JavaScript?

Kotlin is a statically-typed programming language that runs on the Java Virtual Machine and also can be compiled to JavaScript source code.

Does Kotlin work with Openjdk?

Yes. Kotlin is 100% interoperable with the Java programming language and major emphasis has been placed on making sure that your existing codebase can interact properly with Kotlin. You can easily call Kotlin code from Java and Java code from Kotlin. This makes adoption much easier and lower-risk.


3 Answers

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 };
like image 58
Alexey Andreev Avatar answered Oct 16 '22 18:10

Alexey Andreev


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

like image 29
Peter T. Avatar answered Oct 16 '22 18:10

Peter T.


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.

like image 32
Boon Avatar answered Oct 16 '22 18:10

Boon