https://kotlinlang.org logo
#javascript
Title
# javascript
r

rocketraman

03/20/2023, 7:47 PM
I'm trying to use https://github.com/midzer/tobii in my kotlin/js project. However, I'm having problems initializing the library. Right now I'm getting
Uncaught TypeError: components_lightbox_TobiiModule_h0nwsu.Tobii is not a function
-- I'm trying to call the default function defined here: https://github.com/midzer/tobii/blob/production/src/js/index.js#L16. I've tried a couple of different approaches to import the module, code in 🧵.
Attempt #1:
Copy code
@JsModule("@midzer/tobii")
external object TobiiModule {
  fun Tobii(): dynamic
}
Attempt #2:
Copy code
@JsModule("@midzer/tobii")
external fun Tobii(): dynamic
Copy code
Uncaught TypeError: components_lightbox_Tobii_gg6z8a is not a function
    at init_properties_TobiiModule_kt_hm80kh (TobiiModule.kt?29b3:23:13)
    at get_tobii (x.js:45564:5)
    at main (Client.kt?0c95:52:11)
    at eval (x.js:134091:3)
    at eval (x.js:134093:2)
    at ./kotlin/x.js (web.js:1854:1)
    at __webpack_require__ (web.js:2445:33)
    at web.js:3534:37
    at web.js:3537:12
    at webpackUniversalModuleDefinition (web.js:17:17)
t

turansky

03/20/2023, 7:53 PM
Copy code
@file:JsModule("@midzer/tobii")

@JsName("default")
external fun Tobii(): Tobii

sealed external interface Tobii {
   // more members
}
r

rocketraman

03/20/2023, 7:59 PM
fun Tobii()
is
external
right? It's still not doing what I expect, but that's definitely getting me further along, now I can call that function and see that it has executed.
t

turansky

03/20/2023, 8:00 PM
Yes,
external
b

Big Chungus

03/20/2023, 8:57 PM
How about this @JsModule("@midzer/tobii") @JsName("default") external fun Tobii(): dynamic
👍 1
r

rocketraman

03/20/2023, 9:08 PM
Why is the
@Jsname("default")
needed? Is it the symbol used by a common/js module under the hood for the default export?
b

Big Chungus

03/20/2023, 9:16 PM
Yes, it basically links to node's module.exports.default
Default modifier in js is just a syntactic sugar for that
r

rocketraman

03/20/2023, 9:19 PM
Ok. Next problem, I'm trying to pass in a settings object, and I'm using this pattern:
Copy code
@JsModule("@midzer/tobii")
@JsName("default")
external fun Tobii(userOptions: TobiiUserOptions): Tobii

external interface TobiiUserOptions {
  var autoplayVideo: Boolean
}

fun TobiiUserOptions(): TobiiUserOptions = js("{}")

sealed external interface Tobii {
  ...
}
but I'm getting
Uncaught TypeError: components_lightbox_default_mcnppc is not a function
. Any ideas?
b

Big Chungus

03/20/2023, 9:21 PM
How are you invoking Tobii()?
r

rocketraman

03/20/2023, 9:21 PM
Copy code
Tobii(TobiiUserOptions().apply {
  autoplayVideo = true
})
b

Big Chungus

03/20/2023, 9:24 PM
Hmm, can you check if this works first? val opts = js("{}") opts.autoplayVideo = true Tobii(opts)
Also what does this print? @JsModule("@midzer/tobii") external val tmp: dynamic console.log(tmp)
r

rocketraman

03/20/2023, 9:26 PM
No, same problem
b

Big Chungus

03/20/2023, 9:27 PM
console.log above is a neat trick to check module structure when wrapping
r

rocketraman

03/20/2023, 9:27 PM
image.png
default
is a function
b

Big Chungus

03/20/2023, 9:28 PM
And this? @JsModule("@midzer/tobii") @JsName("default") external val tmp: dynamic console.log(tmp)
r

rocketraman

03/20/2023, 9:30 PM
Weird, the same thing
b

Big Chungus

03/20/2023, 9:30 PM
Curious! Ok you've peaked my interest - let me boot up my pc.
👍 1
t

turansky

03/20/2023, 9:35 PM
And this?
@JsModule(“@midzer/tobii”)
@JsName(“default”)
external val tmp: dynamic
console.log(tmp)
In this example
JsName
has no sence 😞
Accoding screenshot function is here:
Copy code
@file:JsModule("@midzer/tobii")

@JsName("default")
external fun Tobii(): Tobii
r

rocketraman

03/20/2023, 9:42 PM
That does seem to work, and accept the
TobiiUserOptions
interface
@turansky Can you give some insight as to why?
b

Big Chungus

03/20/2023, 9:55 PM
Got it working!
Copy code
// tobii.d.kt
@file:JsModule("@midzer/tobii")

@JsName("default")
external class Tobii(userOpts: TobiiUserOpts = definedExternally)

external sealed interface TobiiUserOpts {
  var autoplayVideo: Boolean?
}
Copy code
// main.kt
fun main() {
  val res = Tobii(
    js("{}").unsafeCast<TobiiUserOpts>().apply {
      autoplayVideo = true
    }
  )
  console.log(res)
}
The issue was that js function was basically a js class without the sugar as per their docs
And the
@JsName("default")
is very much required here as otherwise it won't be able to link properly.
r

rocketraman

03/20/2023, 9:59 PM
Yep, I had it working too based on @turansky comment above, but I'm still not sure I understand why. The source of tobii does do
export default function
so why does it not work with the
@JsModule
annotation not at the file level?
b

Big Chungus

03/20/2023, 10:01 PM
Given JS declaration as
export default function Tobii (userOptions) {...}
Tobii
token is completely ignored and is invisible from external modules that import it. It's basically the same as
export default function(userOptions) {..}
from the consumer POV.
r

rocketraman

03/20/2023, 10:01 PM
Ah
b

Big Chungus

03/20/2023, 10:02 PM
It's just a syntactic sugar for this
Copy code
function Tobii(){}
export default Tobii
Tobii
token is never exported, it's just a reference as to what to export.
👍 1
r

rocketraman

03/20/2023, 10:03 PM
So
file:@JsModule
says to look at the file and import the function defined there, so even if
export default Tobii
wasn't there, it would still work?
b

Big Chungus

03/20/2023, 10:05 PM
You're mixing up two things.
@JsModule
imports the whole module object. Used in
@file:JsModule
context it then map declarations inside the file to exported members of the module object
export default XXX
is the same as
export const default = "some value"
(from kotlin POV) and that's why you need @JsName annotation on the function declaration to tell it "even though I have named my function
Tobii
, in js look for function named `default`"
Hope that makes sense.
Some more examples to better illustrate the point
export default <...>
could be the same as any of these
Copy code
export function default() {}
export var default = () => {}
export let default = () => {}
export const default = () => {}
Finally, here's how the declarations should look like without @JsName to work
Copy code
@file:JsModule("@midzer/tobii")

external fun default(): Tobii
//or
external class default()
r

rocketraman

03/20/2023, 10:20 PM
Its not the
@JsName
that I'm confused about right now. What I'm trying to understand is the difference between the
@file:JsModule
declaration vs the
@JsModule
declaration.
The docs say this for a
file:@JsModule
declaration:
Some JavaScript libraries export packages (namespaces) instead of functions and classes. In terms of JavaScript, it's an object that has members that are classes, functions and properties. Importing these packages as Kotlin objects often looks unnatural.
But in this case Tobii index.js does not export a namespace object. Rather, it exports the Tobii function
default
. So why does
Copy code
@JsModule("@midzer/tobii")
@JsName("default")
external fun Tobii(...)
not work?
Or rather, it does work, until you attempt to pass it a
userOptions
parameter
b

Big Chungus

03/20/2023, 10:27 PM
Ah, that one is easier 😀 @JsModule always maps to a js object In @file:JsModule entire file is mapped to said object In @JsModule external val x: dynamic. The x is mapped to the module object All the code module exports is always inside said object
Does that make any sense to you?
Or rather, it does work, until you attempt to pass it a userOptions parameter
That's because it only breaks when you actually try to use the object as a function at runtime. Remember, js has no compile checks
r

rocketraman

03/20/2023, 10:29 PM
Yeah I think so. So the
@file
version is referencing the module that has the function with the parameter. The non-
@file
version is referencing the result of the function after
userOptions
has already been applied to it.
Is that right?
(In this case the Tobii aka "default" function sets some properties on itself and then returns itself)
b

Big Chungus

03/20/2023, 10:33 PM
Not quite. Both versions are referring to the same thing. The only difference is to what the js module object is mapped to. In file case it's mapped to the file itself saying "this file IS the module object and the declarations inside are referring to module object's properties" In non-file case it's just saying "map this external declaration (regardless of the kind) to module object"
Here's all the valid ways to declare the tobii module
Copy code
@JsModule("...")
external object whatever {
  @JsName("default")
  class Tobii()
}
Copy code
@file:JsModule("...")

@JsName("default")
external class Tobii()
Copy code
@JsModule("...")
external val whatever: TobiiModule

external interface TobiiModule {
  @JsName("default")
  class Tobii()
}
Wrong declarations always compile just fine and only break at runtime when you try to use them. This is what I suspect is confusing you.
r

rocketraman

03/20/2023, 10:42 PM
s/class/fun/
I think above right? Ok so the failure when passing properties has nothing to do with what is referenced and everything to do with the declaration just being plain wrong.
b

Big Chungus

03/20/2023, 10:44 PM
Precisely
It should be class according to their docs. Not sure why fun works as well tbh
r

rocketraman

03/20/2023, 10:46 PM
The declaration is a fun that takes an optional parameters object, assigns itself some properties (like a class) and returns itself. So I guess in lovely JavaScript terms it is both a function and a class.
b

Big Chungus

03/20/2023, 10:49 PM
¯⁠\⁠_⁠(⁠ツ⁠)⁠_⁠/⁠¯
Well js classes are always functions in a trench coat. Can't remember why the new keyword is even need there
3 Views