JsName annotation in common code for multiplatform projects


#1

Hello,

We are trying to build a library to be shared between Java and JS.
Behaviour of lib is to be extended in apps which will be using this lib (including in JS).
Because of that I want to controll js naming in common code, which is now impossible.
I saw there was a discussion, but it was abandoned 6 month ago.

Consider following example (simplified):

interface Processor {
    fun doProcess(businessObject: BusinessObject, dataProvider: DataProvider): OperationResult
}

data class BusinessObject (var smth: String ....)

interface DataProvider {
    fun getData(String key): String
}

This shared lib will contain multiple Processor implimentations + BusinessObject definition. It may rely on DataProvider during processing.

I want users in JS to implement DataProvider (on pure JavaScript, not on Kotlin) and I need some clean API for that. Also I want method Processor::doProcess to have same name in JS.
So, I would like to say: "create an JS object with function getData(String) and pass it to one of processor implementations to method doProcess along with BusinessObject".
If I could add @JsName annotation to shared code, I would set fixed name to functions getData and doProcess, but now it cannot be achieved.

Can you please share plans if you are going to add these annotations to stdlib?

Also, any ideas how to workaround that until annotations in stdlib appear - would be much appreciated.


JsName and JVMName cannot be used in the same project
#2

So, I found a ticket to for this. @ilya.gorbunov Any plans to implement this?

As for now I found a cumbersome workaround.
For each interface I want to be implemented in JS, I create corresponding JS interface + class that wraps JS interface into adapter.

So, instead of just adding one annotation I have to write 1. extra interface + 2. class-wrapper.

Code sample

How it would look if @JsName was possible in common code

interface Processor {
    @JsName("process")
    fun process(businessObject: BusinessObject)
}

How it looks now

// in common part
interface Processor {
    fun process(businessObject: BusinessObject)
}
...

// in platform-specific part
interface JsProcessor {
    @JsName("process")
    fun process(businessObject: BusinessObject)
}

class ProcessorAdapter(val jsProcessor: JsProcessor) : Processor {
    fun process(businessObject: BusinessObject) {
        jsProcessor.process(businessObject)
    }
}

#3

@traylz, we are aware of this problem and looking for a solution for it.

Note that currently you can use another workaround. Declare your Processor interface in common code as expected interface:

expect interface Processor {
    fun process(businessObject: BusinessObject)
}

Then in JS code you provide an actual interface Processor with the method annotated with @JsName:

actual interface Processor {
    @JsName("process")
    actual fun process(businessObject: BusinessObject)
}

As a downside this workaround results in a significant code duplication because in every other platform you also have to provide an actual interface for that platform, even if they all are the same:

actual interface Processor {
    actual fun process(businessObject: BusinessObject)
}

#4

In the meantime you can define your own expected annotation in common code that mirrors @JsName (or any of the other similar annotations). In the platform module you can then map this annotation using an actual typealias to the platform specific annotation, and for other platforms you map it to an ignored annotation with source retention so there is not actual impact


#5

In case anyone is struggling with this, here’s some sample code that follows @pdvrieze’s suggestion.

// in commons
expect annotation class Name(val name:String)

// in JVM (this annotation does nothing, ignore it)
actual annotation class Name(actual val name: String)

// in JS
actual typealias Name = kotlin.js.JsName

You can then do

// In commons
class Validator {

    @Name("validate")
    fun validate(user:User): Boolean {
        return true
    }
}

Which will compile to the following JavaScript

  Validator.prototype.validate = function (user) {
    return true;
  };

This isn’t perfect, you need @Name("some_name") on every common method that will be used from JavaScript but with my current limited knowledge of Kotlin multiplatform projects, this is the best solution.

Note to the Jetbrains dev team: (@ilya.gorbunov) has any progress been made on that front? I’m not sure I understand why the names need to be mangled. This approach will break any attempts at progressively migrating from JS to Kotlin as any code that pulls on JS code compiled from Kotlin won’t be able to find the method due to mangling. Or am I missing something?

EDIT:

It appears that this method doesn’t work if you put the @Name annotation on a expect class function. You still need to put the @JsName in the JS actual definition.


#6

We made some progress on discussing the issue and coming to a resolution, see here: https://youtrack.jetbrains.com/issue/KT-18882#comment=27-2800971
The resolution (i.e. optional annotations) is not implemented yet.


#7

A post was split to a new topic: Mandatory method mangling when compiling to JS