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.41k stars 64 forks source link

Provide a way for Kobweb to generate code constants for routes #204

Open bitspittle opened 1 year ago

bitspittle commented 1 year ago

Currently, if you want to link to different routes, you have to do it by String, e.g.

@Page
@Composable
fun SomePage() {
   Link("/path/to/route", "Click me to go to another page")
}

But it could be nice to have type-safe destinations as well, so the code might look like this:

@Page
@Composable
fun SomePage() {
   Link(KobwebRoutes.path.to.route, "Click me to go to another page")
}

Technically, it shouldn't be too hard to do, but designing this right may requires some thinking.

Some issues to consider off the top of my head:

bitspittle commented 1 year ago

Probably worth looking at Compose Navigation as well: https://developer.android.com/jetpack/compose/navigation in case there's anything worth borrowing from their approach

TheDome0 commented 1 year ago

That people still have to find their own way to deal with external URLs makes me think that this is not something kobweb should handle. Unless there is an option to add custom URLs. Also, you would have to consider query parameters, which in contrast to compose navigation can only be strings anyway.

object Routes {
    const val login = "/login"
    fun video(id: Int) = "/video?id=$id"
}

Something like this works for me right now

bitspittle commented 1 year ago

I'm still on the fence whether this feature is worth doing or not. You're right that external URLs would be raw strings anyway.

The main benefit I could see is if you used this and your site got big and then someone changed some routes around, this could give you a compile error instead of a runtime error later that would be easier to miss.

Actually, methods might solve the problem I was worrying about before with name conflicts?

Imagine for my setting example we would generate this:

object Routes {
   fun setting(vararg params: Pair<String, Any>) = ...
   object setting {
      fun admin(vararg params: Pair<String, Any>) = ...
   }
}

Now, the settings method and the settings object don't conflict.

One more idea: the inner objects could be capitalized, which would give us more flexibility too:

object Routes {
   fun setting(vararg params): String = ...
   fun setting(fragment: String, vararg params): String = ...
   val setting: String = ...
   object Setting {
      fun admin(...) = ...
      fun admin(fragment, ...) = ...
      val admin = ...
   }
}

Then the question is if people would be OK with the route settings/admin translating to Routes.Settings.admin or if the mixed capitalization is weird...

bitspittle commented 1 year ago

It would also be possible to do something like this:

interface Route { fun toRouteString() }
fun Link(path: String, text: String) // Already exists
fun Link(route: Route, text: String) = Link(route.toRouteString(), text) // New version

// Assume pages defined for blog/about, settings, and settings/admin
object Routes {
   object blog {
      object about : Route {
         override fun toRouteString("blog/about")
      }
   }
   object setting : Route {
      override fun toRouteString("setting")
      object admin : Route {
         override fun toRouteString("setting/admin")
      }
   }
}

I haven't written the code down, but I'm hoping this could allow both: Link(Routes.setting) and Link(Routes.setting.admin) and also Link(Routes.blog.about) but not Link(Routes.blog)