Rust framework for web development targeting WebAssembly
The programmer knows when a part of the page needs to be updated, no need for code that re-renders things automatically. HTML is treated as strings and the programmer sets the content of a component explicitly. That is the foundation for web applications written in Rust using Kaja Web.
See the lib.rs source file
There are more examples in the examples folder in the repo. Here is one on the server: clickcounter.
The framework is split into three crates:
Install the required dependencies:
cargo add kaja-web cargo add kaja-html-macro cargo add kaja-callback-macro cargo add wasm-bindgen cargo add inventory cargo add web-sys cargo add gloo cargo add js-sys cargo add console_error_panic_hook
Note that you need to specify features when you add web-sys, most features are disabled by default.
Probably in src/lib.rs
use kaja_web::prelude::*;
struct AppData {
current_count: u32,
}
pub static APP_DATA: RwLock = RwLock::new(
AppData { current_count: 0 }
);
fn create_counter_display(count: u32) {
let counter = html! {{
<div class="click-counter-display">$count</div>
<button class="click-counter-increment-button"
onclick="incrementCounter()">
Increment
</button>
}};
try_inner_html("#click-counter", counter.as_str());
}
fn update_count(app_data: &AppData) {
let count = app_data.current_count;
let updated = count.to_string().as_str();
let result = inner_text(".click-counter-display", updated);
if result.is_err() {
log!(".click-counter-display not found");
}
}
#[callback(incrementCounter)]
pub fn increment_counter() {
let mut app_data = APP_DATA.write().unwrap();
let updated = app_data.current_count.saturating_add(1);
app_data.current_count = updated;
update_count(&app_data);
}
#[wasm_bindgen(start)]
pub fn init() {
init_callbacks();
create_counter_display(0);
}
index.html
<script
type="module"
src="clickcounter.js" defer>
</script>
<body>
<main id="click-counter"><main>
<body>
wasm-pack build \
--release \
--target web \
--out-dir pkg \
--out-name clickcounter \
--no-typescript
cp index.html pkg/
Change --release to --debug if you want good symbols in the panic handler.
Enable panic handler if the current build is a debug build. Compile with --debug and add this to your code:
#[wasm_bindgen(start)]
pub fn init() {
std::panic::set_hook(
Box::new(console_error_panic_hook::hook),
);
console_error_panic_hook::set_once();
init_callbacks();
}
There are several ways to store and share the state of a WASM program in Rust. One option is using an Rc<RefCell<T>> to hold your object; another is using an RwLock. I prefer RwLock. When the JS engine calls your Rust code, take a read or write lock on your AppData object in the callback function and pass references to it in other functions (& or &mut).
The rule to follow is: lock the app state object in callback functions and pass references around in all non-callback functions that need the data.
use kaja_web::prelude::*;
use std::sync::RwLock;
struct AppData {
current_count: i32,
}
pub static APP_DATA: RwLock = RwLock::new(
AppData {
current_count: 0
}
);
fn other_function(app: &mut AppData) {
app.update_view();
}
#[callback(updateCallbackFunction)]
pub fn update_callback_function() {
let mut app_data = APP_DATA.write().unwrap();
other_function(&mut app_data);
}
Email: johan.mattsson.m@gmail.com
Mastodon:
typo.social/@birdfont
Github:
johanmattssonm
Linkedin:
johan-mattsson-2ab324112