https://kotlinlang.org logo
#decompose
Title
# decompose
n

Nacho Ruiz Martin

12/08/2023, 6:10 PM
👋 What's the recommended approach, if any, to use decompose components from a react app written in typescript? I don't think my web devs are ready to jump into Compose for Web.
a

Arkadii Ivanov

12/08/2023, 6:24 PM
I'm not sure if a Kotlin project is supposed to be consumed from JS. With all its name mangling, publishing to npm, etc. You can check my experience with Reaktive: https://github.com/badoo/Reaktive/issues/488 But you can write React UI in Kotlin and use Decompose: https://github.com/arkivanov/Decompose/tree/master/sample/app-js
n

Nacho Ruiz Martin

12/08/2023, 7:33 PM
I’m not sure if a Kotlin project is supposed to be consumed from JS.
That’s a pity 😞. I thought this sentence in the docs was trying to express that this is something prepared (screenshot). But I guess that’s referring to Kotlin/JS React, right? Also, I had some hope because of this: https://kotlinlang.org/docs/js-modules.html
But you can write React UI in Kotlin and use Decompose:
Yes, I can try to convince them to switch to Kotlin but I think it’ll be harder 😅 .
a

Arkadii Ivanov

12/08/2023, 8:00 PM
Sorry for the confusion, I've updated the docs with wording "Kotlin/React".
gratitude thank you 1
You can actually try exporting your shared Kotlin module to JS. Maybe it will be usable!
n

Nacho Ruiz Martin

12/08/2023, 8:00 PM
I'll try and let you know the results :)
Is Kotlin/Wasm on the scope?
a

Arkadii Ivanov

12/08/2023, 8:06 PM
Is Kotlin/Wasm on the scope?
Yes, I'm trying to add support for WASM in the next Decompose 3.0.0-alpha01
n

Nacho Ruiz Martin

12/08/2023, 8:41 PM
Awesome news!! 👏
K 1
I guess the quick answer is no sad panda .
Not only
Value
, basically nothing can be exported.
sealed class
, for example.
a

Arkadii Ivanov

12/09/2023, 9:56 AM
Maybe ask in #javascript?
n

Nacho Ruiz Martin

12/09/2023, 10:01 AM
I will, but low faith on that, honestly!
#javascript went silent, but experimenting with the system I achieved to, at least, export the component into a npm package usable from a JS app. The problem? Since Decompose classes are not marked as exportable to JS with
@JsExport
, the compiler is not available to type those properties and are exported as `any`:
Copy code
//Typescript
export declare interface RootBloc /* extends Bloc<RootContract.State, RootContract.Event> */ {
    readonly childStack: any/* Value<ChildStack<UnknownType *, RootBlocChild>> */; <-- LOOK AT THIS ANY
    onDeepLink(link: string): void;
    readonly __doNotUseOrImplementIt: {
        readonly "app.spoiless.shared.root.RootBloc": unique symbol;
    };
}
export declare function initRoot(appVersionName: string, appEnvironment: string): void;
This is kind of usable but certainly cumbersome. Do you think we could do something to Decompose to address this issue and if it would be worth it?
a

Arkadii Ivanov

12/09/2023, 6:03 PM
Yep, whether it is worth it or not. That's the question. I have never seen any library annotated with JsExport.
When you export, who Decompose is packed? Is it bundled or published as a separate package somehow?
I remember issues with it
n

Nacho Ruiz Martin

12/09/2023, 6:06 PM
It is bundled inside the npm package:
I have never seen any library annotated with JsExport.
Yep, basically nothing is marked with JsExport. I guess Kotlin/JS was never though to be used from JS?
a

Arkadii Ivanov

12/09/2023, 6:10 PM
I guess Kotlin/JS was never though to be used from JS?
Correct. And it feels like going against the wind 🙂 I will try playing with it though.
I believe similar issues will be with stdlib types like List, right?
n

Nacho Ruiz Martin

12/09/2023, 6:11 PM
Hah, you’re right. Not even stdlib is exported.
a

Arkadii Ivanov

12/09/2023, 6:12 PM
Found a similar question, but no resolution: https://kotlinlang.slack.com/archives/C0B8L3U69/p1688652623763079
n

Nacho Ruiz Martin

12/09/2023, 6:12 PM
I don’t think this is the way no red
Yes, I didn’t even want to start thinking about the reactive streams.
I will try playing with it though.
Don’t feel the pressure. I’m giving up this approach. I’ll go down the “Hey, please, learn to write your code in React for Kotlin, it’s not that different” path.
K 1
a

Arkadii Ivanov

12/09/2023, 6:26 PM
I’ll go down the “Hey, please, learn to write your code in React for Kotlin, it’s not that different” path
Indeed! 🙂
Do you need to support any other platforms apart of JS?
Soooo, I discovered a "way". Not ideal, but should work for any library, not only Decompose. For Decompose, if you add the attached "Wrappers.kt" file, then you can use it as in the example.
Untitled.cpp
This is how it gets exported.
n

Nacho Ruiz Martin

12/09/2023, 9:51 PM
Sorry for the late reply, Arkadii. Only JS apart from iOS and Android is needed. Wow, that looks promising. It feels a bit like polluting the otherwise quite clean code of the rest of platforms... I'll give it a try and see how much I can simplify. Thanks very much for the idea 👍.
👍 1
a

Arkadii Ivanov

12/27/2023, 10:15 AM
Discovered an interesting article that touches on this topic: https://kt.academy/article/ak-js-interop
n

Nacho Ruiz Martin

12/27/2023, 10:58 AM
Absolutely interesting, thanks! Same idea about wrappers. It's clumsy to create a wrapper for each class that you want to expose to not pollute the code, but at the same time is really scalable since it'll be easy to remove bit by bit of the wrapper until Kotlin/JS covers everything. It also brought some ideas regarding how to consume the module from TS. Thanks, Arkadii! I'm slowly but steadily progressing on the matter and my Decompose components are almost ready to be used from Next, so that's good. Do you think it'd make sense to create a companion library (or Gradle plugin) to expose all these objects for people facing the same problem as me with Decompose.
a

Arkadii Ivanov

12/27/2023, 11:21 AM
I think it would be beneficial if you publish a library for Decompose for this specific case. Later we could consider exposing types directly from Decompose if there is demand for this use case. Thanks!
It would be also nice to list here the complete set of types you need to be exported from Decompose. Maybe it's actually a good idea to export them directly from Decompose. If not too many.
Wondering, if there are any downsides of exporting types for people who don't need this (i.e. writing fully Kotlin apps).
In other words, why can't the compiler export all public types by default?
n

Nacho Ruiz Martin

12/27/2023, 1:06 PM
I'll work on a preliminary list of types that need to be exported 👍. Regarding why not everything is exported, I'm unsure, to be honest. And it's something I was also wondering about. The only downside I can think of is that the generated TS can get huge rapidly or that they can clash in naming if ESModules are used since it's a flat structure. Here's a thread related to this, but there isn't a real explanation. I might query the KJS team about this, I guess they must have a reason.
This is the minimum list I think would be needed: • Value • Cancellation • Child • ChildStack • ChildSlot • ChildPages • LifecycleRegistry • ComponentContext? -> In case you want to invoke the component constructor from JS (I prefer to make a helper function in Kotlin for this, but…) That’s all that is usually exposed from a component, right? I might be missing some things I haven’t used!
a

Arkadii Ivanov

12/27/2023, 4:59 PM
Yeah, seems reasonable. It looks like there should be a way to export third-party things, according to the discussion - https://kotlinlang.slack.com/archives/C0B8L3U69/p1703682488880949?thread_ts=1703682488.880949&amp;cid=C0B8L3U69
n

Nacho Ruiz Martin

12/27/2023, 5:17 PM
Yeah, I'm following that, just letting you masters do the chat 😅. This helper library would only make sense in case the KJS transitive exporting takes more time than expected.
Hey, Arkadii. Do you think it makes sense to wait for the KJS team to work on transitive exports or it would be beneficial to create the wrappers library?
a

Arkadii Ivanov

01/08/2024, 2:27 PM
I'm still thinking about marking those types with JsExport. 😀
n

Nacho Ruiz Martin

01/08/2024, 2:37 PM
Ok! That would make life easier for sure, haha 👍 . Let me know anytime if you are leaning more towards the wrappers package and I can contribute.
a

Arkadii Ivanov

01/08/2024, 2:39 PM
Sure, will do!
n

Nacho Ruiz Martin

01/08/2024, 3:02 PM
Sure thing
a

Arkadii Ivanov

01/08/2024, 9:10 PM
I assume that annotating things with JsExport would only affect the size when exporting a library? It won't affect if you just build a 100% Kotlin/JS app?
n

Nacho Ruiz Martin

01/09/2024, 12:58 PM
I'm not sure about that. I think it'll affect the size of the generated package even if only targeting KJS. I can make a quick test or ask the KJS team.
a

Arkadii Ivanov

01/09/2024, 1:03 PM
A quick test would be really helpful! If you don't mind.
n

Nacho Ruiz Martin

01/09/2024, 1:03 PM
Sure
🙏 1
Wow, yeah, the KJS app is definitely impacted. It’s 3 times bigger just by adding @JsExport to a single dummy object.
a

Arkadii Ivanov

01/09/2024, 1:55 PM
Wow, how come? I think I will need to check it myself as well. Curious what contributes to the size. What steps have you used?
n

Nacho Ruiz Martin

01/09/2024, 1:57 PM
The setup is a KJS app module depending on a dummy module that has a single object with some constants. Building the app with jsBrowserDistribution outputs a package of ~3kb. Then I add JsExport to the object and the new output is almost 9kb.
👍 1
Both the main .js and the .js.map files are way bigger.
a

Arkadii Ivanov

01/09/2024, 2:14 PM
What's the diff?
n

Nacho Ruiz Martin

01/09/2024, 3:09 PM
Ah… Mac is doing something really wrong calculating sizes. The files are identical.
K 1
a

Arkadii Ivanov

01/09/2024, 3:10 PM
Nice! Thanks for the update! This sounds promising.
👍 1
gratitude thank you 1
n

Nacho Ruiz Martin

01/09/2024, 3:24 PM
Sorry for the back and forth. Mistakes were made on my side. The files are different indeed 😓. Although, I think the difference in size being three times bigger is because the project is really small and it’s adding info related to the annotation. Here’s the diff of the not minified version of the package:
Quite unreadable in that format, maybe you can use some tool?
I mean that this will be added only once and not per-annotated class, so the size shouldn’t increase that much in bigger projects.
The difference is in bytes, btw.
a

Arkadii Ivanov

01/09/2024, 3:27 PM
I will try, thanks a lot!
👍 1
I tried creating an empty JS app module that depends on a library module. The library module contains one class with one empty function. The app just instantiates the class and calls the function. After running
:app:sBrowserDistribution
without exporting anything, the size of the resulting
app.js
file is 2,067 bytes. After running
:app:sBrowserDistribution
with the class exported, the size of the resulting
app.js
file is 2,197 bytes. Am I doing something wrong?
n

Nacho Ruiz Martin

01/10/2024, 7:43 AM
I don’t think so, no. That’s the same procedure on my side 🤔. Have you tried generating the development package instead?
a

Arkadii Ivanov

01/10/2024, 10:06 PM
jsBrowserDevelopmentExecutableDistribution
produces
app.js
file of size 824Kb without exporting, and 826Kb with one class exported from the shared module.
I'm a bit surprised that we've got different results. Maybe you could upload a sample project where you are observing the big difference?
Also, maybe we can avoid exporting Essenty (e.g. Lifecycle and LifecycleRegistry)? Perhaps, if we export just Decompose things, you can expose a function creating the root component from your library? Also, this way you wouldn't need to expose implementation details, like
DefaultRootComponent
class.
Copy code
@JsExport
fun rootComponent(): RootComponent {
    val lifecycle = LifecycleRegistry()
    val root = DefaultRootComponent(componentContext = DefaultComponentContext(lifecycle = lifecycle))
    lifecycle.attachToDocument()
    return root
}

private fun LifecycleRegistry.attachToDocument() {
    fun onVisibilityChanged() {
        if (document.visibilityState == DocumentVisibilityState.visible) {
            resume()
        } else {
            stop()
        }
    }

    onVisibilityChanged()

    document.addEventListener(type = EventType("visibilitychange"), callback = { onVisibilityChanged() })
}
Also,
ChildStack
contains
List
properties, which cannot be exported. We would need to add separate array properties like
backStackArray
and
itemsArray
.
n

Nacho Ruiz Martin

01/11/2024, 1:45 PM
Thanks for this, will take a look. My sample is inside a quite complex project, I’ll try to create a blank one and see if I’m getting the same results than you.
you can expose a function creating the root component from your library?
Yes, this can be a way, for sure. It would be cool to allow JS users to control the lifecycle of a component on their behalf, though. But this is only useful in some edge cases, I guess.
Also,
ChildStack
contains
List
Yes, indeed, I had to wrap things into a native array in the wrappers I’m using.
Ok, checked the PR and it looks great -simple and effective. I’ll test it on my own and let you know. Thanks, mate!
a

Arkadii Ivanov

01/11/2024, 1:53 PM
Yeah! So the main question now is how it affects the size for all-in-Kotlin cases.
Also noticed that JsExport doesn't work for classes/interfaces on Wasm yet.
n

Nacho Ruiz Martin

01/11/2024, 1:57 PM
Yep. WASM support of JsExport is quite low right now. But it’s planned, AFAIK: https://kotlinlang.org/docs/wasm-js-interop.html#kotlin-wasm-and-kotlin-js-interoperability-differences
So the main question now is how it affects the size for all-in-Kotlin cases.
I’ll create a basic project with and without these changes and let you know.
🙏 1
a

Arkadii Ivanov

01/12/2024, 1:00 PM
Unfortunately, some tests are failing due to https://youtrack.jetbrains.com/issue/KT-64927. I will try to find a workaround, but I have doubts. This means that annotating a type with JsExport can potentially break existing code that extends that type, due to the bug.
n

Nacho Ruiz Martin

01/12/2024, 2:11 PM
Ah… 😓 . Bleeding edge technologies bleeding 🩸.
🥹 1
a

Arkadii Ivanov

01/24/2024, 1:24 PM
Woop woop! https://youtrack.jetbrains.com/issue/KT-34995 just was marked as fixed, which probably means we should have exported collections soon.
n

Nacho Ruiz Martin

01/24/2024, 1:26 PM
Noice! One step closer 😊
👍 1
33 Views