Static Sites
There are several ways to generate static sites with Bonito. The main one is:
Bonito.export_static
— Functionexport_static(html_file::Union{IO, String}, app::App)
export_static(folder::String, routes::Routes)
Exports the app defined by app
with all its assets a single HTML file. Or exports all routes defined by routes
to folder
.
The simplest one, which also allows an interactive Revise based workflow is enabled by interactive_server
:
Bonito.interactive_server
— Functioninteractive_server(f, paths, modules=[]; url="127.0.0.1", port=8081, all=true)
Revise base server that will serve a static side based on Bonito and will update on any code change!
Usage:
using Revise, Website
using Website.Bonito
# Start the interactive server and develop your website!
routes, task, server = interactive_server(Website.asset_paths()) do
return Routes(
"/" => App(index, title="Makie"),
"/team" => App(team, title="Team"),
"/contact" => App(contact, title="Contact"),
"/support" => App(support, title="Support")
)
end
# Once everything looks good, export the static site
dir = joinpath(@__DIR__, "docs")
# only delete the bonito generated files
rm(joinpath(dir, "bonito"); recursive=true, force=true)
Bonito.export_static(dir, routes)
For the complete code, visit the Makie website repository which is using Bonito: MakieOrg/Website
When exporting interactions defined within Julia not using Javascript, one can use, to cache all interactions:
Bonito.record_states
— Functionrecord_states(session::Session, dom::Hyperscript.Node)
Records widget states and their UI updates for offline/static HTML export. This function captures how the UI changes in response to widget interactions, allowing exported HTML to remain interactive without a Julia backend.
How it works
Each widget's states are recorded independently:
- The function finds all widgets in the DOM that implement the widget interface
- For each widget, it records the UI updates triggered by each possible state
- The resulting state map is embedded in the exported HTML
Widget Interface
To make a widget recordable, implement these methods:
is_widget(::YourWidget) = true # Marks the type as a recordable widget
value_range(w::YourWidget) = [...] # Returns all possible states
to_watch(w::YourWidget) = w.observable # Returns the observable to monitor
Limitations
- Large file sizes: Recording all states can significantly increase HTML size
- Independent states only: Widgets are recorded independently. Computed observables that depend on multiple widgets won't update correctly in the exported HTML
- Performance: Not optimized for large numbers of widgets or states
Example
# This will work - independent widgets
s = Slider(1:10)
c = Checkbox(true)
record_states(session, DOM.div(s, c))
# This won't fully work - dependent computed observable
combined = map((s,c) -> "Slider: $s, Checkbox: $c", s.value, c.value)
record_states(session, DOM.div(s, c, combined)) # combined won't update