:wave: What's the recommended approach, if any, to...
# decompose
n
šŸ‘‹ 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
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
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
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
I'll try and let you know the results :)
Is Kotlin/Wasm on the scope?
a
Is Kotlin/Wasm on the scope?
Yes, I'm trying to add support for WASM in the next Decompose 3.0.0-alpha01
n
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
Maybe ask in #javascript?
n
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
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
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
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
Hah, youā€™re right. Not even stdlib is exported.
a
Found a similar question, but no resolution: https://kotlinlang.slack.com/archives/C0B8L3U69/p1688652623763079
n
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
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
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
Discovered an interesting article that touches on this topic: https://kt.academy/article/ak-js-interop
n
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
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
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
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
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
I'm still thinking about marking those types with JsExport. šŸ˜€
n
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
Sure, will do!
n
Sure thing
a
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
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
A quick test would be really helpful! If you don't mind.
n
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
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
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
What's the diff?
n
Ahā€¦ Mac is doing something really wrong calculating sizes. The files are identical.
K 1
a
Nice! Thanks for the update! This sounds promising.
šŸ‘ 1
gratitude thank you 1
n
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
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
I donā€™t think so, no. Thatā€™s the same procedure on my side šŸ¤”. Have you tried generating the development package instead?
a
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
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
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
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
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
Ahā€¦ šŸ˜“ . Bleeding edge technologies bleeding šŸ©ø.
šŸ„¹ 1
a
Woop woop! https://youtrack.jetbrains.com/issue/KT-34995 just was marked as fixed, which probably means we should have exported collections soon.
n
Noice! One step closer šŸ˜Š
šŸ‘ 1