I have been playing with `Kotlin/React` for a few ...
# javascript
d
I have been playing with
Kotlin/React
for a few days and I found it interesting. I was very amazed by the chance of creating HTML/CSS using Kotlin DSL rather than plain HTML/CSS. It is very powerful, as the the HTML and CSS are created automatically in an optimized way, without having to organize CSS classes yourself. However I was disappointed by the
performance
and by the
js filesize
. I am working on a multiplatform project, with a shared ViewModel, which on Android is plugged into a JetpackCompose UI and on iOS into SwiftUI. I am looking for the best way to use my declarativeUI-ready ViewModel on web too, via
StateFlow
. My basic requirements for a web framework are these 5: • collect a StateFlow emitted by the KMP shared ViewModel • create HTML components using Kotlin DSL • use a decent variety of already made web components • react to ViewModel state changes quickly • compile to a small JS filesize I just came across two interesting web frameworks written in Kotlin: `Fritz2`: https://www.fritz2.dev/ `KVision`: https://kvision.gitbook.io/ I would be very interesting to hear the experiences of people here, and how they compare them in terms of performance and filesize to React/Kotlin:
r
I'm the author of KVision.
Generally JS bundle size is an issue with all Kotlin/JS frameworks.
JS versions of Kotlin libraries like coroutines and serialization are large and even with DCE webpack minification is not as optimized as it could be.
d
@Robert Jaros what is the typical JS filesize of a medium project using KVision, StateFlow and Ktor?
do you have some benchmarks between KVision and Kotlin/React, in terms of performance and filesize?
r
I'm not even using Ktor client with KVision, because it adds a tremendous amount of code to the bundle. KVision has it's own Rest client.
d
Is the Rest client wrapping JS fetch?
r
This application https://peup.finn.pl/ is build with KVision and uses serialization, coroutines and kotlinredux Kotlin libraries (and of course a lot of NPM JS libraries as well). It's bundle size is 715KB gzipped (3,2MB uncompressed).
It's large, but usable - it's being used successfully in production.
KVision Rest client is in fact based on JQuery
It was easier to create, as jQuery is already included as a required dependency
KVision is modular, it has a lot of modules, so you only need to include the modules you want to use.
Mini-template project from https://github.com/rjaros/kvision-examples/tree/master/mini-template repo doesn't use any optional modules. It's bundle size is 120KB gzipped (470KB uncompressed). It's probably the smallest you can get at the moment.
KVision still uses legacy compiler backend because IR is not working as expected (bundle size is larger - see KT-41227).
But I hope eventually it will get smaller with IR 🙂
d
thanks for the info!
r
A for your other questions - KVision has two interchangable redux modules for state management (one for redux.js and one for reduxkotlin.org). But it has also other reactive data components (observables) and a module with Flow/StateFlow extensions.
It has HTML DSL and a lot of ready to use components.
You can also use external React components.
As for performance, I haven't done any real benchmarks, but I think it's a bit slower than plain React.
It's also general Kotlin/JS issue - see KT-24784
d
interesting, so hopefully things can only improve
a
Kotlin-react without ktor client weights about 200 kB, it is not much heavier than react itself. Ktor client is indeed quite heavy. The performance of kotlin react should be the same as basic react since it is a very thin wrapper. The react itself (without kotlin) is far from ideal though. So I think to try fritz next time. Kvision also looks quite interesting, but it is focused on full-stack and databinding, which I do not need.
r
I would say fritz2 is way more focused on databinding then KVision. When it comes to state and data, KVision is trying to be as universal and unopinionated as possible.
a
I agree. I am yet to try kvision. For now I only saw examples. What I probably want in the end is something like tornado with a reactive properties-based distributed model and easy-to-add components. The recent addition of react components in kvision makes it possible to add those components I already developed. So I will try it sooner or later. Not much time right now, sadly.
j
@Daniele B what performance issues are you experiencing? I use the react library with an MVVM architecture with Kodein-DI for dependency injection. Any ViewModel exposes a viewState which is a StateFlow and I collect it as a React state. Using the correct hooks might deal with your performance issues, as react tends to re-render a lot if you use it wrongly. In terms of performance my site renders/interacts fluently. In terms of file size, that is a different story 😅 I am still waiting for Kodein to support the IR backend, there are file size improvements promised by Jetbrains using the new IR backend.
d
@Joost Klitsie I am using StateFlow too, in the way I described here. Am I setting it up correctly? https://youtrack.jetbrains.com/issue/KT-42129
j
I use it like this:
Copy code
fun <T> StateFlow<T>.collectAsState(
    context: CoroutineContext = Dispatchers.Unconfined
): T {
    val (state, updateState) = useState<T> { this@collectAsState.value }
    useLayoutEffectWithCleanup(emptyList()) {
        val job = CoroutineScope(context).launch {
            collect {
                updateState(it)
            }
        }
        return@useLayoutEffectWithCleanup {
            job.cancel()
        }
    }
    return state
}
it is similar as what compose does
I think you should use the emptyList() so it doesn't recreate itself all the time
also you don't have to check for
Copy code
if (it != appState) {
because the stateFlow is anyway conflated
I wouldn't use the global dispatcher as well, for me it turned out that using the unconfined dispatcher did the trick
Copy code
val viewModel by instance<EventOverviewContract.Props, EventOverviewContract.ViewModel>( // Injecting my viewmodel and providing props to it
            EventOverviewProps(
                handler = props.handler
            )
        )
        val viewState = viewModel.viewState.collectAsState() // Now I can render my component base on the viewState
d
@altavir do you have some figure on how much size Ktor adds to a js file vs to just using js fetch?
@Joost Klitsie interesting! I will try it out soon. It sounds like a good implementation.
a
@Daniele B About 0.8 MB if I remember correctly
d
@altavir quite a lot indeed. I was expecting that JetBrains was capable of removing all unnecessary code in case Ktor is just used as a simple http client.
a
It is not about unnecessary code, ktor uses quite heavy buffer polling mechanics that is quite useful for high-load application. It is possible, it is an overkill for simple fetch.
I agree that it would be nice to have something lightweight with ktor API
d
Maybe an issue should be open to request that
I just opened one: @altavir
r
It's a problem with almost all Kotlin multiplatform libraries. It's quite easy to write something with tons of useful features, because it's not an issue with non-JS targets. No one even tries to modularize or optimize ;-/ But in the end it's hard to use these libraries in the browser, because of the generated JS size. When developing KVision I had this problem number of times - I had to drop coroutines dependency from the core package, drop ktor client, korlibs websocket client, korlibs klock library - and reimplement my own, thin versions. And kotlinx.serialization, which I just couldn't drop because there is no other alternatives, is responsible for almost 30% of the bundle size (even though I use only very simple functions).
j
I think it is a problem with kotlin not being able to properly tree-shake (DCE/dead code elementation) on Javascript, or common DCE not properly working together with Kotlin transpiled to Javascript. With the IR compiler this should be improved as then it can do optimizations on the intermediate representation. Also kotlin could apparently do other optimizations, like supporting ES6 or issues like this https://youtrack.jetbrains.com/issue/KT-37710 So just blaming a library here is taking it the easy way. Perhaps the library could be better split into small components, or Kotlin could do better to optimize its outputs (what should happen in theory with the IR compiler)
176 Views