Search code examples
javascriptkotlinkotlin-js-interop

How to set a static property on a class in Kotlin for Javascript


I have a situation where I need to define a static property on a class in Kotlin and when its compiled to Javascript have it become a true static field on that class. In this situation companion objects do not work.

For example, if I have an abstract class and its implementing class like below:

abstract class MyAbstractClass{
  abstract val id: Int
}

class MyClass: MyAbstractClass(){
  override val id: Int = 1 //I want this to actually be "static" on the MyClass
}

The Javascript that this compiles down to is this:

function MyAbstractClass() {
}

function MyClass() {
  MyAbstractClass.call(this);
  this.id_jz5fma$_0 = 1;
}

Object.defineProperty(MyClass.prototype, 'id', {
  get: function () {
    return this.id_jz5fma$_0;
  }
});

But what I need it to compile down to is this:

function MyAbstractClass() {
}

function MyClass() {
  MyAbstractClass.call(this);
}

MyClass.id = 1;

So that the id field does actually statically exist on MyClass without having to make a new instance of MyClass.

I've tried using a companion object but that creates a separate object/function called MyClass$Companion and then assigns the id field to that and never actually assigns it statically to MyClass.

How can I go about setting true static fields like this in Kotlin?


Solution

  • Right now we don’t have a direct way to do it, so I’ve created issue https://youtrack.jetbrains.com/issue/KT-18891

    As a workaround, you can write a function like that:

    inline fun <reified T : Any> addStaticMembersTo(source: Any) {
        val c = T::class.js.asDynamic()
        val ownNames = js("Object").getOwnPropertyNames(source) as Array<String>
        val protoNames = js("Object").getOwnPropertyNames(source.asDynamic().constructor.prototype) as Array<String>
    
        for (name in ownNames + protoNames) {
            c[name] = source.asDynamic()[name]
        }
    }
    

    And use like:

    class A {
        companion object {
            init {
                addStaticMembersTo<A>(object {
                    val bar = 1
                    fun foo() {}                
                })
            }
        }
    }
    

    or even make companion object's members available as a static member of class:

    class B {
        companion object {
            val bar = 1
            fun foo() {}                
    
            // should be at the end of companion object
            init {
                addStaticMembersTo<B>(this)
            }
        }
    }
    

    The full example available here: https://try.kotl.in/#/UserProjects/uube1qikg3vsegtnefo0ad0jag/30f1qf87dt5k5vjhciirt4t108