Lets talk about magic `fun js(code: String): dynam...
# javascript
s
Lets talk about magic
fun js(code: String): dynamic
function. [https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.js/js.html#js] It allows you to embed JavaScript code to Kotlin. Its one of those features which is super simple to use in most cases but becomes quite complex when specifying what it does in general. It has some problems. It implicitly links Kotlin declarations with names used inside JavaScript snippet, and can become an effective tool to shoot yourself in a foot. For most interop needs safer constructs like external declarations and dynamic expressions can be used instead. It is also a tough feature to maintain: it requires a whole JavaScript parser to be included and parser result integrated with compiler data structures. Switching parsers can be hard. Supporting multiple ES versions only adds to complexity. We are considering various options to redesign it in future releases to make it “less powerful”. We would like to hear your input to make sure we got you covered with a smooth transition: Do you use
js
function a lot? What for? Are they big? What constructs do you put inside?
t
i tried to use it, but found it quite hard fore some reasons... In the end i'm mostly using it to create empty objects an arrays. espeically the second one i think s quite handy because you can to
Copy code
val array = js("[]")
array.push("something")
which feels far more lightweight then trying to to the same in pure kotlin 😄
j
99% of my usage is for
{}
so i can create an arbitrary object. there's an YouTrack issue for a map-like facade which would obviate its need.
c
I’ve got a helper function that I use for making object literals, so I can avoid the
js()
function altogether.
Copy code
inline fun obj(block: dynamic.() -> Unit): dynamic {
    return object{}.apply<dynamic>(block)
}

// example usage
val options = obj {
    url = "<http://api.gdax.com/products/BTC-USD/ticker>"
    json = true
    headers = obj {
        this["User-Agent"] = "request"
    }
}
a
at a quick look, I’ve used it for: • writing
delete obj[property]
• passing a js regex • calling Object.keys and Object.values • dereferencing the Symbol.iterator property of an object wouldn’t surprise me if there were ways to do these without using js(), I didn’t necessarily look hard (or since finding one solution months ago)
j
I'm not sure what the advantage to using
object {}
is there. And I suspect it would cause a class to be created in JS.
s
@Casey Brooks Cant you just use
jsObject
for that ?
c
Using that function does not create a class underneath, it directly maps to a JS object literal. I’ve successfully used it with several external libraries that are configured with object literals, where any solutions using classes or maps would fail
@spand I am not familiar with
jsObject
, it doesn’t seem to be in the stdlib (or maybe I’m just missing it)
@jw using
object{}
works to avoid using the
js()
function
j
But why? Replace
object{}
with
js("{}"}
and your function works exact the same except it's now cheaper and faster in the actual JS
s
Was sure it came from stdlib but I can see its from https://github.com/JetBrains/kotlin-wrappers/tree/master/kotlin-extensions
s
object{}
currently does create a “class” without any members. It behaves similar to JS object literal (with a bit of overhead) if you add properties to its instance in
dynamic
way.
👍 1
r
I'm using
js("Object").keys
to enumerate properties of dynamic objects.
And for some surprising reason I'm using something like this in my tests
js("new Date(2017,2,14,14,50,35).getTime()")
. I have no idea why 😉
Wouldn't it be easier to just make "advanced"
js("....")
function, that just inlines the given text to the resulted javascript code? Without any parser or checking? To just assume a developer knows what he/she is doing?
s
This is one of the options we are considering
👍 2
j
honestly that's how i thought it already worked 😮
1
r
I wouldn't call it "less powerful" 🙂
s
But this “just text” approach would prevent you from using Kotlin local variables and parameters inside. They might get renamed or optimized away.
👍 1
g
Just checked, using it for config objects
Copy code
val themeProps: ThemeOptions = js("({palette: { type: 'placeholder', primary: {main: 'placeholder'}}, typography: {useNextVariants: 'placeholder'}})")
r
@Svyatoslav Kuzmich [JB] I could imagine string interpolation for variable references.
Copy code
val variable = ...
val result = js("jsfunction(${variable})")
It would insert the correct variable name (even if it's mangled in resulting code).
It would be enough to work with local variables. I could create one for anything else.
a
My use of
js
is probably not very inspiring. I do not have a lot of experience with JavaScript. So there are some constructs from examples that I just can't translate into kotlin. In this case I just use
js
call for the whole construct.
g
@Svyatoslav Kuzmich [JB] Maybe for those local variables and parameters provide an
@JsName
annotation so that they are not mangled by the compiler? Then you can use those exact names in the
js("...")
call.
s
Its hard to guarantee a name for local variable. Couple examples: • Variable can have the same name as some function used in the same JS scope. It must not shadow it. • Variable inside inline function can be inlined into scope that already has the same name.
g
We use it very few times inside data2viz, mostly to test the existence of sowe properties or objects. Here are some exemples:
Copy code
private fun getPixelRatio(): Double{
    var pixelRatio = 1.0
    js("""
        if((typeof window.devicePixelRatio) !== 'undefined') {
            pixelRatio = window.devicePixelRatio
    }
    """)

    return pixelRatio
}
Copy code
internal actual fun delegateNow(): Double = (if (performanceAvailable) js("performance.now()") as Double else Date.now())

val performanceAvailable:Boolean = js("((typeof performance === 'object') && performance.now) ? true : false ") as Boolean
a
We have a KMPP setup that spits out npm packages designed to be consumed from JS. We use
js(...)
in a few integration tests to (try to) make sure that things work as expected in JS-land after being DCEd (i.e., method names we rely on aren't mangled if we forget to add a
@JsName
annotation, things we need aren't stripped out if we forget to add them to the
dceOptions.keep
)