Has anybody already written codegen to create over...
# javascript
s
Has anybody already written codegen to create overloads that accept an
external interface
of the args to allow for js-style object arguments? I’m imagining:
Copy code
@JsConstructor
@JsExport
data class ImportantData(foo: String, bar: Int)
Would end up generating JS-only code like:
Copy code
external interface ImportantDataArgs {
  val foo: String
  val bar: Int
}

inline fun createImportantData(args: ImportantDataArgs): ImportantData {
  return ImportantData(foo = args.foo, bar = args.bar)
}
That would ultimately generate a JS function that would be typed so that you could do:
Copy code
createImportantData({
  foo: "hello",
  bar: 123
});
Does something like this already exist?
e
In don't think so, but I'm also thinking "why?" looking at the example. Maybe I'm not getting the full picture. Btw, you'd need
jso { ... }
for that last snippet to work.
a
I'm not quite sure that it's what you need, but I created a plugin for JS called
js-plain-objects
: https://github.com/JetBrains/kotlin/tree/master/plugins/js-plain-objects So, your code will look like:
Copy code
@JsPlainObject
external interface ImportantDataArgs {
  val foo: String
  val bar: Int
}

fun main() {
  val args = ImportantDataArgs(foo = "hello", bar = 123) // just a regular js object
  println(JSON.stringify(args)) // { foo: "hello", bar: 123 }
}
Unfortunately, it works only with K2.
e
I think the goal here is being able to write plain objects exactly like JS. So for example
Copy code
const x = { 
  one: 1,
  two: 2,
}
You can do it with the external interface and the
jso
helper function
Copy code
val x = jso<ImportantDataArgs> { 
  one = 1
  two = 2
}
Although, I find this not idiomatic to Kotlin, and I'd prefer the plain object solution that is implemented for K2.
Btw, I was told K/JS IDE support for K2 will land in 2024.2
a
But, this is what the
@JsPlainObject
does. It compiles to exact JS object:
Copy code
// Kotlin
val args = ImportantDataArgs(foo = "hello", bar = 123)

// JavaScript
var args = { foo: "hello", bar: 123 }
✔️ 1
c
Can't you already write this?
Copy code
createImportantData(jso {
  foo = "hello"
  bar = 123
});
Sure, it's not exactly the same syntax, but it's fine, no?
Maybe the collections literal KEEP could help with this.
e
Didn't even know there was a KEEP issue for that, or is it TBD?
c
I believe there is one and I remember reading it somewhat recently? But I can't find it at the moment 😅
s
Ah, sorry it wasn’t clear from my post. The last snippet is meant to be javascript code that consumers of my library could write. The goal is for consumers of my library, to be able to call functions and/or create objects with named arguments. The idiomatic way to do that in JS, is by passing an object as a single argument. So I want folks to be able to write JS (not Kotlin) like this:
Copy code
const important = createImportantData({
  foo: "hello",
  bar: 123
});
Instead of what they have to do today which is:
Copy code
const important = new ImportantData("hello", 123)
Does that make more sense?
c
Ah, I see. However, I do believe this already works for simple objects? Plain JS objects are really not great though, e.g. they don't carry inheritance information, so the result won't really be an instance of the class.
s
Yeah, it would just be so nice if I could provide that as a convenience for all constructors and functions. I may take a stab at a plugin when I get some time.
e
My opinion is you'll complicate your life. For a couple of spots it might be ok tho
s
Cool! Now all we need is the codegen to create that for args based on an annotation!
h
How does JsPlainObject work with libraries? Does a consumer also enable the plugin? Or do you need another import?
Copy code
@JsPlainObject
external interface InputOptions {
    var required: Boolean?
    var trimWhitespace: Boolean?
}
I enabled the plugin in my library as well as in the consumer project but I get this JS code:
Copy code
InputOptions;
  throwLinkageError("Function 'invoke' can not be called: No function found for symbol 'com.github.actions/InputOptions.Companion.invoke|invoke(kotlin.Boolean?;kotlin.Boolean?){}[0]'");
e
Good question, I don't know the answer tbh
@hfhbd did you find a way to make it work?
h
Nope
a
Hey @hfhbd. No, it's designed to be required only in place of usage of the
JsPlainObject
annotation, so, its is not required to apply the plugin on the consumer side (
e
But would adding the plugin two times (consumer + imported library) result in an error?
a
No, it should not
e
Thanks! I think that's what happening on @hfhbd side, am I right?
a
Could you create a ticket, I will try to fit a fix into 2.0.0
a
I've reproduced the issue and prepared a fix. The issue is major, so I will try to cherry-pick it into 2.0.0
gratitude thank you 1
Hey @hfhbd. Seems like it should work with RC2, could you check it please?
h
Yes, it does work now.
🫡 1
e
Nice to know, thanks!
But how are you managing to use it without IDE support
h
I didn't talk about IDE support 😄
e
Ahahaha makes sense
h
AFAIK we need to wait for 2024.2 to have K2 MP support
✔️ 1
c
To solve the original issue to codegen JS friendly functions for consumers of KMP libraries, we created the
@JsNamedArgs
annotation: https://github.com/PhiloInc/JsNamedArgs This annotation and compiler plugin codegens external interfaces and new functions that accept the interface as an input, all of which is then exported to JS. This has made things a bit easier for our JS developers to use our KMP library! Hope it can help other as well!
e
@Casey Morris that's cool! I'll have a look in the morning, but you should send it on the channel too.