v0.19.3
Some fun miscellaneous items in this one, including breakpoint ranges, Kotlinx serialization support for HTTP verbs, and support for small/large/dynamic viewport units.
Important
Planning to upgrade? Review instructions in the README.
Changes
Frontend
- Fixed development live reload behavior, which now works even if the tab is not visible when the site is finished rebuilding.
- HTTP fetch methods now support serialization.
- See "HTTP verb serialization" notes section for more details.
- You can now specify breakpoint ranges in CSS styles.
- For example:
(Breakpoint.SM until Breakpoint.LG) { ... }
targets tablet and desktop screens (but no wider). - The README breakpoints section has been updated with more examples.
- For example:
- New
Modifier.attr
andModifier.dataAttr
attribute modifiers.Modifier.attr("key", "value")
is a slightly simpler substittute forModifier.attrsModifier { attr("key", "value") }
Modifier.dataAttr("key", "value")
is short forModifier.attrsModifier { attr("data-key", "value") }
- Added support for repainting canvases manually.
- See "Repainting canvases manually" notes section for more details.
- New CSS units for small / large / dynamic viewport sizes
- These modern (and relatively recent) units let designers create sites that respond better to mobile devices that have UI elements that hide / show regularly.
- See this article for examples.
- Fixed a reported issue where attempting to query
AppGlobals
inInitSilk
andInitKobweb
methods would crash. ⚠️ routePrefix
has been renamed tobasePath
- You will be warned to update this value in your
conf.yaml
file. - This name is more consistent with what other web frameworks use.
- You will be warned to update this value in your
- Simplified a bunch of APIs that used to take
autoPrefix
parameters.- It is not expected that any user outside of the Kobweb library internally actually used this parameter, but they have been removed. At this point, if you set a
basePath
value in yourconf.yaml
file, you should just expect that Kobweb will automatically add it every time.
- It is not expected that any user outside of the Kobweb library internally actually used this parameter, but they have been removed. At this point, if you set a
Backend
- Updated ktor to v3.0.0
- And migrated / cleaned up code related to SSE (server-sent events), which is used for live reloading.
- Fixed an issue with spaces in paths in our
start.bat
server script. - Server start scripts now include the server layout
- In other words,
kobweb export --layout fullstack
andkobweb export --layout static
generate slightly different scripts.
- In other words,
Build
- Jetbrains Compose dependency updated to 1.7.1
- Disable compose trace markers for kobweb modules.
- This was extra compose code that had no effect for Compose HTML sites, so disabling it should result in some JS file size savings.
Notes
HTTP verb + serialization
The HTTP standard provides a core set of verbs (GET, POST, PUT, etc.) which Kobweb provides Kotlin bindings for. For example:
// `get` returns a ByteArray, which we convert into a string. It is up to us to interpret that further,
// perhaps by parsing it
val unparsedData = window.api.get("/user/123").decodeToString()
With this latest release, if your site depends on kotlinx serialization (setup instructions here) and a kobweb serialization artifact (discussed shortly), you can leverage it to get a fully parsed type.
First, you should add a dependency on the artifact that provides the utility methods:
# libs.versions.toml
[libraries]
kobwebx-serialization-kotlinx = { module = "com.varabyte.kobwebx:kobwebx-serialization-kotlinx", version.ref = "kobweb" }
// site/build.gradle.ktx
// apply kotlinx serialization plugin
jsMain.dependencies {
// ...
// add kotlinx serialization dependency
implementation(libs.kobwebx.serialization.kotlinx)
}
And that point, your project should have access to kotlinx-serialization-aware methods:
@Serializable
class UserDataResponse(val id: String, val name: String, val age: Int)
val userData = window.api.get<UserDataResponse>("/user/123")
check(userData.id == "123")
You can also pass in rich types for the body parameter, if you have one:
@Serializable
class ChangeUserNameRequest(val id: String, val name: String)
@Serializable
class ChangeUserNameResponse(val error: String?)
val response = window.api.put<ChangeUserNameRequest, ChangeUserNameResponse>(
"/user/change-name", body = ChangeUserNameRequest("123", "Kobweb")
)
if (response.error != null) { /* ... */ }
Repainting canvases manually
Canvases (specifically, Canvas2d
and CanvasGl
widgets) were designed to repaint continuously, which was honestly inspired by my background in gamedev. However, it is absolutely valid to have a canvas that only paints once and then stops until the user interacts with the canvas, or at least, whose repaint is infrequent and controlled by the developer.
To this end, we introduced the new CanvasRepainter
class, which you can optionally pass into a canvas and use it to control when that canvas rerenders:
// Before: Continuously rendering canvas
Canvas2d(width, height, minDeltaMs = ONE_FRAME_MS_60_FPS) {
/* rendered about every 15ms */
}
// New: Repaint on click
val repainter = remember { CanvasRepainter() }
Canvas2d(width, height, Modifier.onClick { repainter.repaint() }, repainter = repainter) {
/* rerendered every time the user clicks on this canvas */
}
Here is an example which refills the canvas with a random color every click:
const val WIDTH = 320
const val HEIGHT = 240
// Later...
val repainter = remember { CanvasRepainter() }
Canvas2d(
WIDTH, HEIGHT,
Modifier.onClick { repainter.repaint() },
repainter = repainter
) {
ctx.fillStyle = Color.rgb(Random.nextInt(255), Random.nextInt(255), Random.nextInt(255))
ctx.fillRect(0.0, 0.0, WIDTH.toDouble(), HEIGHT.toDouble())
}
If you don't care about repainting at all and just want a canvas that renders something unique once and then never again, you can pass in the REPAINT_CANVAS_MANUALLY
value to the minDeltaMs
parameter and then just never request a repaint:
Canvas2d(width, height, minDeltaMs = REPAINT_CANVAS_MANUALLY) {
/* renders exactly once */
}
A note on tryRoutingTo
/ navigateTo
and base paths
If you have RoutePrefix.prepend
code anywhere in your project (which is probably very few of you all):
ctx.router.tryRoutingTo(RoutePrefix.prepend("/"))
it is no longer necessary to auto-prepend anything yourself. In this release, tryRoutingTo
and navigateTo
have been updated to prepend these values for you automatically.
In other words, you can find those calls and simplify your code to:
ctx.router.tryRoutingTo("/")
and it will just work.
Old code will continue to work for now, but if you look at your dev console output, you'll find warnings asking the developer to search your code and fix this issue.
If you have code where you still need to keep the RoutePrefix.prepend
call in (because, say, you're interacting with a Compose HTML widget that is not aware of Kobweb base paths), the name has been deprecated and you should change it to BasePath.prepend
instead.
Thanks!
-
Thanks to new contributor @ChrisGNZ for reporting and helping us fix the issue with our
start.bat
script (which broke if the user's Java path had at least one space in it...) -
Thanks to @dead8309 for their spooky Hacktoberfest contribution, fixing #602 and giving us serialization APIs for HTTP verbs.
-
Thanks to @TheDome0 for their help extending CSS units.
-
And to @DennisTsar for so much of his work, as usual, but with a special callout for the ktor 3.0.0 migration, removing unnecessary trace markers (resulting in smaller sites for everyone), and some work he has done in preparation for Kotlin 2.1.0 (to be supported in a future release)
Full Changelog: v0.19.2...v0.19.3