livid-rs is a Rust framework for creating frontend and backend wasm web apps (targetting browsers and desktop). It's composed of:
-
livid: A lightweight frontend Rust crate for creating web apps via webassembly. It provides the following advantagges:
- Backend agnostic.
- Thin wrapper around web-sys
- No vdom.
- No macros!
-
livid-cli: A lightweight build/dev tool which allows compiling your frontend into a web app or desktop app (via
livid deploy
). It can be invoked from the command line:
livid build
livid serve
# if targetting desktop
livid deploy
-
livid-desktop: A minimal webview implementation in Rust, which allows deploying your wasm web app as desktop apps. It's also frontend agnostic.
-
livid-server: An internal server used in livid-cli and livid-desktop
-
Examples:
- Different architectures (low-level, widgets api, elm architecture).
- Fullstack livid application
- livid frontend with a wry backend.
- The wasm32-unknown-unknown target:
rustup target add wasm32-unknown-unknown
- Install the livid-cli crate (to simplify building and bundling your web app):
cargo install livid-cli
- You can add links to CSS files/urls, and use Widget::set_class_name() to benefit from CSS styling.
- Create a project:
[dependencies]
livid = "0.2"
In your Rust source file:
use livid::{enums::*, prelude::*, *};
enum Action {
Increment(i32),
Decrement(i32),
}
fn btn(action: Action) -> button::Button {
let (label, color, step) = {
match action {
Action::Increment(v) => ("Increment", Color::Green, v),
Action::Decrement(v) => ("Decrement", Color::Red, -v),
}
};
let btn = button::Button::default().with_label(label);
btn.set_label_size(20);
btn.set_label_color(color);
btn.set_margin(10);
btn.set_padding(10);
btn.set_frame(FrameType::RFlatBox);
btn.add_callback(Event::Click, move |_| {
let frame = widget::Widget::from_id("result").unwrap();
let mut old: i32 = frame.text_content().unwrap().parse().unwrap();
old += step;
frame.set_text_content(Some(&old.to_string()));
});
btn
}
fn main() {
let col = group::Column::default_fill();
col.set_justify_content(AlignContent::Center);
btn(Action::Increment(1));
let f = frame::Frame::default().with_label("0").with_id("result");
f.set_padding(20);
f.set_label_size(20);
btn(Action::Decrement(1));
col.end();
}
- Build and serve using livid-cli:
livid build
or livid serve
- Build a desktop app with wasm for the frontend using:
livid deploy --width=600 --height=400
Livid also a lower level widgets api:
use livid::{enums::*, prelude::*, *};
fn div() -> widget::Widget {
widget::Widget::new(WidgetType::Div)
}
fn btn(i: i32) -> widget::Widget {
let btn = widget::Widget::new(WidgetType::Button);
let (label, col) = if i > 0 {
("Increment", "Green")
} else {
("Decrement", "Red")
};
btn.set_text_content(Some(label));
btn.set_style(Style::Color, col);
btn.add_callback(Event::Click, move |_| {
let result = widget::Widget::from_id("result").unwrap();
let mut old: i32 = result.text_content().unwrap().parse().unwrap();
old += i;
result.set_text_content(Some(&old.to_string()));
});
btn
}
fn main() {
document::Document::get().set_title("Counter");
let btn_inc = btn(1);
let btn_dec = btn(-1);
let main_div = div();
main_div.append(&btn_inc);
main_div.append(&btn_dec);
let result = div();
result.set_id("result");
result.set_text_content(Some("0"));
result.set_style(Style::FontSize, "22px");
let btns = document::Document::get().get_elements_by_tag_name("BUTTON");
for btn in btns.iter() {
// set their fontSize to 22 pixesl
btn.set_style(Style::FontSize, "22px");
}
}
use livid::{
document::Document,
enums::WidgetType::{self, *},
widget::Widget,
};
fn w(typ: WidgetType) -> Widget {
Widget::new(typ)
}
fn main() {
Document::get().set_title("Form");
Document::add_css_link("https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css");
Document::add_css_link("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css");
let form = w(Form);
form.set_class_name("box");
form.append(&{
let div = w(Div);
div.set_class_name("field");
div.append(&{
let label = w(Label);
label.set_class_name("label");
label.set_inner_html(r#"<span class='fa fa-envelope'></span> Email"#);
label
});
div.append(&{
let div = w(Div);
div.set_class_name("control");
div.append(&{
let inp = w(Input);
inp.set_class_name("input");
inp.set_attribute("type", "email").unwrap();
inp.set_attribute("placeholder", "m@gmail.com").unwrap();
inp
});
div
});
div
});
}
Livid is unopinionated. You can build higher-level abstractions on top:
mod detail; // we define an OnEvent trait for our buttons
use crate::detail::{OnEvent, App, Settings};
use livid::{enums::*, prelude::*, *};
#[derive(Default, Copy, Clone)]
struct Counter {
value: i32,
}
#[derive(Debug, Clone, Copy)]
enum Message {
IncrementPressed,
DecrementPressed,
}
impl App for Counter {
type Message = Message;
fn new() -> Self {
Self::default()
}
fn title(&self) -> String {
String::from("Counter - livid")
}
fn update(&mut self, message: Message) {
match message {
Message::IncrementPressed => {
self.value += 1;
}
Message::DecrementPressed => {
self.value -= 1;
}
}
}
fn view(&mut self) {
let col = group::Column::default_fill();
button::Button::default()
.with_label("Increment")
.on_trigger(Message::IncrementPressed);
frame::Frame::default().with_label(&self.value.to_string());
button::Button::default()
.with_label("Decrement")
.on_trigger(Message::DecrementPressed);
col.end();
}
}
fn main() {
Counter::new().run(Settings {
size: (300, 200),
win_color: Some(Color::Rgb(Rgb(250, 250, 250))),
..Default::default()
})
}
Check the examples directory for fuller examples.
livid-cli builds and bundles your wasm web app.
cargo install livid-cli
- wasm32-unknown-unknown target:
rustup target add wasm32-unknown-unknown
- Wasm-bindgen:
cargo install wasm-bindgen-cli
- Wasm-opt (optional, wasm-opt is called if found in PATH and the --release flag is passed to
livid build
).
It can be downloaded from the binaryen repo's releases.
USAGE:
livid <SUBCOMMAND>
SUBCOMMANDS:
build Build your wasm web app
clean Clean output artifacts
serve Serve the generated index.html
deploy Creates a desktop app using the wasm web app for frontend
--help Prints this message or the help of the given subcommand(s)
From the manifest directory of your wasm rust applications directory, run:
livid build
or livid build --release
It should generate a dist folder in the manifest directory. The generated html index file can then be served by any server or by running livid serve
. If you prefer to use another server for development, pass the dist directory to the server:
python3 -m http.server --dir dist
If livid-cli finds an index.html shell in the manifest directory, it will use it and replace the {{SCRIPT}}
placeholder (à la emscripten shell html) with a script to load the generated js glue code.
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
{{SCRIPT}}
</body>
</html>
To create a desktop app:
livid deploy --width=800 --height=600 --title="app name"
livid-desktop = "0.1"
In your main.rs
use livid_desktop::{App, Settings};
fn main() {
let a = App::new(Settings {
w: 600,
h: 400,
title: "My App",
fixed: true,
port: 8080, // the default
dist_folder: PathBuf::from("dist"), // the default
..Default::default()
});
a.run();
}
The dist folder should contain the index.html plus the wasm and javascript glue code.
- On Windows: No other dependencies.
- On MacOS: No other dependencies.
- On X11/wayland platforms, webkit2gtk:
- Debian-based distros: sudo apt-get install libwebkit2gtk-4.0-dev.
- RHEL-based distros: sudo dnf install webkit2gtk3-devel.