There are several ways to generate static sites with Bonito. The main one is:
export_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:
interactive_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
sourceWhen exporting interactions defined within Julia not using Javascript, one can use, to cache all interactions:
record_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 monitorLimitations
Experimental Feature
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