kobweb
kobweb copied to clipboard
Provide a way for Kobweb to generate code constants for routes
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:
- Web URL names are more flexible than Kotlin variable names
- e.g.
/1/2/3would probably correspond toKobwebRoutes._1._2._3
- e.g.
- The solution needs to handle ambiguities
- e.g.
/1and/_1could be different routes but would both translate toKobwebRoutes._1 - It's possible we just make this an error and force people to update their names
- e.g.
- Routes can be both destinations and parent folders
- e.g.
settingsandsettings/adminare both valid routes, but you can't have some Kotlin object that supports bothKobwebRoutes.settingsas a string but alsoKobwebRoutes.settings.adminat the same time - Possibly we can do something like:
KobwebRoutes.settingsandKobwebRoutes.settings_admin? Is that ugly?
- e.g.
- I don't think we should always generate this code; it should probably be opt-in if people don't need it. This could be done with a
kobweb { ... }block setting OR just make the logic for generating this file be a separate task that people can run manually- e.g. `./gradlew :mysite:generateRouteConstants
- Alternately, just always generate it anyway assuming that DCE removes it if people don't use it?
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
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
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...
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)