varabyte / kobweb

A modern framework for full stack web apps in Kotlin, built upon Compose HTML
https://kobweb.varabyte.com
Apache License 2.0
1.46k stars 65 forks source link

Use ksp when it supports js(IR) #4

Closed bitspittle closed 1 year ago

bitspittle commented 2 years ago

See also: https://github.com/google/ksp/issues/33

For now, maybe just hack things by parsing "manually" with like a regex or something?

bitspittle commented 1 year ago

Here's some code that can used as a starting point. Thanks @DVDAndroid for the initial work!

https://github.com/DVDAndroid/kobweb/tree/dvd/ksp

bitspittle commented 1 year ago

So I finally got a chance to look into this, nearly one year later :)

With the way Kobweb has evolved, it's possible that KSP may not be the approach we want to use after all. Let me document my thoughts here for now and I'll sleep on them.


Background

Currently, codegen works through a Gradle plugin which, when applied, adds a bunch of tasks, including kobwebGenSiteSource (main.kt for frontend), kobwebGenApi (ApisFactoryImpl.kt for backend), kobwebGenSiteIndex (index.html), kobwebExport, and kobwebStart.

The project parsing is handled by the kotlin-compiler-embeddable artifact.

There's also a markdown plugin which adds kobwebxMarkdownConvert, which needs to run before kobwebGenSiteSource, as it may generate files that feed into it as inputs. Currently, this is easy to do because with Gradle, you just register one task as a dependency of another.


If we transition to KSP, the Kobweb application plugin doesn't go away! You still need kobwebStart, etc.

The only tasks that might go away are kobwebGenSiteSource and kobwebGenApi. So now we'd have this weird complicated thing where a Kobweb project needs to both specify a KSP processor AND a gradle plugin, which both need the other active to be effective.

Another wrinkle -- with kobwebGenSiteSource, we output different source based on whether we're targeting debug or release. I don't see a way to control that behavior with KSP very easily. Even if there is a way, we have to make sure we keep the Gradle and KSP logic in sync (since currently Gradle parses a property from the command line and turns that into a build release value).

And finally, we have markdown -> Kotlin (from the markdown plugin), which definitely isn't related to KSP (KSP doesn't care about markdown). So now we'd have some gen source tasks handled by KSP and some by Gradle.

All in all, I'm starting to think that KSP isn't the way forward here. I think it makes a Kobweb project...

The only reason I can think that we'd want to use KSP over Gradle is performance. But I'm not sure that the Kotlin parsing we're currently doing is that slow. Maybe I need to time stuff, but unless we're talking about 5-10s slimming down to sub-second, I'm not sure it's worth the complexity!

bitspittle commented 1 year ago

I'm going to close this for now. I think having the Gradle plugin continue to do the parsing is the easiest path forward.

Otherwise, I'd need to ask users to both apply a Gradle plugin (e.g. com.varabyte.kobweb.library) and a ksp plugin + ksp("com.varabyte.kobweb:processor") dependency.

In the future, if we find out that compile time additions from the Kobweb plugin are prohibitive, then we can revisit this decision.

As an aside, thanks to my notes from the above comment, I did significantly refactor my Gradle code, so that the code parsing / processing step is simultaneously cleaner and it also caches intermediate outputs which means that 1) subsequent task runs which do expensive parsing might not need to happen and 2) doing an export will only run the parsing step once (before, it was running it twice in a row from different tasks. whoops!)

bitspittle commented 1 year ago

One thing that might make this worth revisiting is KSP probably has better support for resolving values?

Like, if I have

val SomeVariant = SomeStyle.addVariant("...")

and I want to know the fully qualified path of SomeStyle, I believe KSP lets me resolve that (even though it's generally discouraged as an expensive operation)

With the embedded kotlin compiler artifiact, I'm not 100% sure I can do that. So I end up with some checks I can't do at compile time...