[!NOTE] This package is under active development and its functionality may change over time.
freshwater provides server-side rendering utilities for plumber2 backends:
- composable HTML templates (slots, parameters, and fragments)
- template caching
- weak ETag caching
- shiny tag serialisation1
- CSRF protection
For autoreloading support, consider hotwater.
Installation
You can install the development version of freshwater from GitHub with:
# install.packages("pak")
pak::pak("ElianHugh/freshwater")Reusable templates
library(freshwater)
details <- template(name, age, {
div(
p(sprintf("Name: %s", name)),
p(sprintf("Age: %s", age))
)
})
details("Jim", 30)Fragments
card <- template(title, {
div(
h2(title),
fragment(
div("Card body"),
name = "body"
),
fragment(
div("Footer"),
name = "footer"
)
)
})
card("Hello")
card("Hello", fragment = "body")Layouts & content injection
Attribute name normalisation
Attribute names with non-leading underscores are rewritten to hyphenated HTML attributes Double underscores (__) act as an escape hatch for literal underscores
template({
div(
hx_get = "/items",
hx_target = "#main",
data_user_id = 42,
`data__raw__name` = "keep_underscore",
"Load"
)
})()Cached partials
Cached partials are keyed by template, fragment, cache name, and vary.
Nested Caches
dashboard <- template(page, stats, {
cache(
"page",
vary = page$updated_at,
div(
h1("Dashboard"),
cache(
"stats",
vary = stats$updated_at,
p(stats$count)
)
)
)
})
dashboard(
page = list(updated_at = 1),
stats = list(updated_at = 2, count = 42)
)
dashboard(
page = list(updated_at = 1),
stats = list(updated_at = 2, count = 42)
)Conditional GETs
If the client’s If-None-Match header matches the current ETag, freshwater returns 304 Not Modified and skips rendering.
#* @get /dashboard
#* @etag \() as.integer(Sys.Date())
function() {
page_main()
}CSRF Protection
Prevent hijacking unsafe HTTP methods via the double-submit cookie pattern
function(api) {
api |>
api_csrf(secure = TRUE)
}