stdweb is a standard library for the client-side Web.
The goal of the crate is to provide Rust bindings to the Web APIs and to allow a high degree of interoperability between Rust and JavaScript.
Here are it's design goals:
- Expose a full suite of Web APIs as exposed by web browsers.
- Try to follow the original JavaScript conventions and structure as much as possible, except in cases where doing otherwise results in a clearly superior design.
- Be a building block from which higher level frameworks and libraries can be built.
- Make it convenient and easy to embed JavaScript code directly into Rust and to marshal data between the two.
- Integrate with the wider Rust ecosystem, e.g. support marshaling of structs which implement serde's Serializable.
- Put Rust in the driver's seat where a non-trivial Web application can be written without touching JavaScript at all.
- Allow Rust to take part in the upcoming WebAssembly (re)volution.
- Make it possible to trivially create standalone libraries which are easily callable from JavaScript.
Step 1: Install
Add the following line to your Cargo.toml file:
stdweb = "0.4.20"
Step 2: Use
You can directly embed JavaScript code into Rust:
let message = "Hello, 世界!";
let result = js! {
alert( @{message} );
return 2 + 2 * 2;
};
println!( "2 + 2 * 2 = {:?}", result );
Closures are also supported:
let print_hello = |name: String| {
println!( "Hello, {}!", name );
};
js! {
var print_hello = @{print_hello};
print_hello( "Bob" );
print_hello.drop(); // Necessary to clean up the closure on Rust's side.
}
You can also pass arbitrary structures thanks to serde:
#[derive(Serialize)]
struct Person {
name: String,
age: i32
}
js_serializable!( Person );
js! {
var person = @{person};
console.log( person.name + " is " + person.age + " years old." );
};
Full Examples
Here are some full stdweb usage examples:
Example 1: Canvas Example
main.rs
extern crate stdweb;
use stdweb::traits::*;
use stdweb::unstable::TryInto;
use stdweb::web::{
document,
window,
CanvasRenderingContext2d
};
use stdweb::web::event::{
MouseMoveEvent,
ResizeEvent,
};
use stdweb::web::html_element::CanvasElement;
// Shamelessly stolen from webplatform's TodoMVC example.
macro_rules! enclose {
( ($( $x:ident ),*) $y:expr ) => {
{
$(let $x = $x.clone();)*
$y
}
};
}
fn main() {
stdweb::initialize();
let canvas: CanvasElement = document().query_selector( "#canvas" ).unwrap().unwrap().try_into().unwrap();
let context: CanvasRenderingContext2d = canvas.get_context().unwrap();
canvas.set_width(canvas.offset_width() as u32);
canvas.set_height(canvas.offset_height() as u32);
window().add_event_listener( enclose!( (canvas) move |_: ResizeEvent| {
canvas.set_width(canvas.offset_width() as u32);
canvas.set_height(canvas.offset_height() as u32);
}));
canvas.add_event_listener( enclose!( (context) move |event: MouseMoveEvent| {
context.fill_rect(f64::from(event.client_x() - 5), f64::from(event.client_y() - 5)
, 10.0, 10.0);
}));
stdweb::event_loop();
}
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>stdweb • Canvas</title>
<style>
html, body, canvas {
margin: 0px;
padding: 0px;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="canvas.js"></script>
</body>
</html>
Example 2: Drag Drop Example
main.rs
#[macro_use]
extern crate stdweb;
use std::sync::{Arc, Mutex};
use stdweb::traits::*;
use stdweb::web::{
document,
IParentNode,
};
use stdweb::web::event::{
DataTransfer,
DataTransferItemKind,
DragOverEvent,
DragStartEvent,
DragDropEvent,
EffectAllowed,
DropEffect,
};
use stdweb::unstable::TryInto;
pub fn remove_str(vec: &mut Vec<String>, item: &str) -> Option<String> {
let pos = vec.iter().position(|x| *x == *item)?;
Some(vec.remove(pos))
}
fn show_latest_drop(dt: DataTransfer) {
dt.items().index(0).unwrap().get_as_string(|s| {
let latest_drop_elem =
document()
.query_selector(".latest-drop")
.unwrap()
.unwrap();
js! {
@{latest_drop_elem.as_ref()}.innerHTML = @{s} + " just moved to the other team!";
};
});
}
fn add_event_listeners<F>(team: &'static str, change_team: F)
where F : Fn(&str) + 'static {
let chars_elem = document().query_selector(&format!(".{}-chars", team)).unwrap().unwrap();
chars_elem.add_event_listener(|e: DragStartEvent| {
let target = e.target().unwrap();
let char_name: String = js!(return @{target.as_ref()}.textContent).try_into().unwrap();
e.data_transfer().unwrap().set_effect_allowed(EffectAllowed::CopyMove);
e.data_transfer().unwrap().set_data("text/plain", char_name.as_ref());
});
let dropzone_elem = document().query_selector(&format!(".{}-dropzone", team)).unwrap().unwrap();
dropzone_elem.add_event_listener(|e: DragOverEvent| {
e.prevent_default();
e.data_transfer().unwrap().set_drop_effect(DropEffect::Move);
});
dropzone_elem.add_event_listener(move |e: DragDropEvent| {
e.prevent_default();
let content = e.data_transfer().unwrap().get_data("text/plain");
show_latest_drop(e.data_transfer().unwrap());
change_team(&content);
});
}
fn render(team_a: &Vec<String>, team_b: &Vec<String>) {
let inner_html = |vec: &Vec<String>|
vec
.iter()
.map(|x| format!("<div class=\"char\" draggable=\"true\">{}</div>", x))
.collect::<Vec<String>>()
.join("\n");
;
let team_a_elem = document().query_selector(".team-a-chars").unwrap().unwrap();
let team_b_elem = document().query_selector(".team-b-chars").unwrap().unwrap();
js!(@{team_a_elem.as_ref()}.innerHTML = @{inner_html(team_a)});
js!(@{team_b_elem.as_ref()}.innerHTML = @{inner_html(team_b)});
}
fn drag_and_drop_elements_example() {
let team_a_arc = Arc::new(Mutex::new(vec![
String::from("Mario"),
String::from("Fox"),
]));
let team_b_arc = Arc::new(Mutex::new(vec![
String::from("Marth"),
String::from("Captain Falcon"),
]));
let change_team = |name: &str,
team_a: &mut Vec<String>,
team_b: &mut Vec<String>,
to_a: bool| {
remove_str(team_a, name);
remove_str(team_b, name);
if to_a {
team_a.push(String::from(name));
} else {
team_b.push(String::from(name));
}
render(team_a, team_b);
};
let team_a = team_a_arc.clone();
let team_b = team_b_arc.clone();
add_event_listeners("team-a", move |name: &str| {
let mut team_a = team_a.lock().unwrap();
let mut team_b = team_b.lock().unwrap();
change_team(name, &mut *team_a, &mut *team_b, true);
});
let team_a = team_a_arc.clone();
let team_b = team_b_arc.clone();
add_event_listeners("team-b", move |name: &str| {
let mut team_a = team_a.lock().unwrap();
let mut team_b = team_b.lock().unwrap();
change_team(name, &mut *team_a, &mut *team_b, false);
});
let team_a = team_a_arc.clone();
let team_b = team_b_arc.clone();
render(&*team_a.lock().unwrap(), &*team_b.lock().unwrap());
}
fn drop_filesystem_example() {
let dropzone = || document().query_selector("#filesystem-dropzone").unwrap().unwrap();
dropzone().add_event_listener(move |e: DragOverEvent| {
e.prevent_default();
js!(@{e.as_ref()}.currentTarget.style.backgroundColor = "lightblue");
e.data_transfer().unwrap().set_drop_effect(DropEffect::Move);
});
dropzone().add_event_listener(move |e: DragDropEvent| {
e.prevent_default();
js!(@{e.as_ref()}.currentTarget.style.backgroundColor = "transparent");
let div = document().create_element("div").unwrap();
js!(@{div.as_ref()}.innerHTML = "content of dataTransfer.items:");
dropzone().append_child(&div);
for x in e.data_transfer().unwrap().items() {
let div = document().create_element("div").unwrap();
let kind_str = match x.kind() {
DataTransferItemKind::String => String::from("a string"),
DataTransferItemKind::File => String::from("a file"),
ref kind if kind.as_str() == "other" => String::from("an expected other kind"),
kind => format!("an unexpected other kind \"{}\"", kind.as_str()),
};
js!(@{div.as_ref()}.innerHTML = @{format!("- {} of type {}", kind_str, x.ty())});
dropzone().append_child(&div);
};
let div = document().create_element("div").unwrap();
js!(@{div.as_ref()}.innerHTML = "content of dataTransfer.files:");
dropzone().append_child(&div);
for x in e.data_transfer().unwrap().files() {
let div = document().create_element("div").unwrap();
js!(@{div.as_ref()}.innerHTML = "- a file named " + @{x.name()});
dropzone().append_child(&div);
}
let hr = document().create_element("hr").unwrap();
dropzone().append_child(&hr);
});
}
fn main() {
stdweb::initialize();
drag_and_drop_elements_example();
drop_filesystem_example();
stdweb::event_loop();
}
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>stdweb • Drag and Drop</title>
<style>
</style>
</head>
<body>
<div>
<h1>Drag and drop characters</h1>
<div class="latest-drop"></div>
<div class="team-a-dropzone">
<h2>Team A</h2>
<div class="team-a-chars"></div>
</div>
<div class="team-b-dropzone">
<h2>Team B</h2>
<div class="team-b-chars"></div>
</div>
</div>
<hr/>
<div id="filesystem-dropzone" style="min-height: 200px; padding: 2em; border: 1px solid black;">
<strong>Drop anything here (e.g. files from your file system)</strong>
</div>
<script src="drag.js"></script>
</body>
</html>
Example 3: Echo Example
main.rs
extern crate stdweb;
use std::rc::Rc;
use stdweb::traits::*;
use stdweb::unstable::TryInto;
use stdweb::web::{
HtmlElement,
document,
WebSocket,
};
use stdweb::web::event::{
KeyPressEvent,
SocketOpenEvent,
SocketCloseEvent,
SocketErrorEvent,
SocketMessageEvent,
};
use stdweb::web::html_element::InputElement;
// Shamelessly stolen from webplatform's TodoMVC example.
macro_rules! enclose {
( ($( $x:ident ),*) $y:expr ) => {
{
$(let $x = $x.clone();)*
$y
}
};
}
fn main() {
stdweb::initialize();
let output_div: HtmlElement = document().query_selector( ".output" ).unwrap().unwrap().try_into().unwrap();
let output_msg = Rc::new(move |msg: &str| {
let elem = document().create_element("p").unwrap();
elem.set_text_content(msg);
if let Some(child) = output_div.first_child() {
output_div.insert_before(&elem, &child).unwrap();
} else {
output_div.append_child(&elem);
}
});
output_msg("> Connecting...");
let ws = WebSocket::new("wss://echo.websocket.org").unwrap();
ws.add_event_listener( enclose!( (output_msg) move |_: SocketOpenEvent| {
output_msg("> Opened connection");
}));
ws.add_event_listener( enclose!( (output_msg) move |_: SocketErrorEvent| {
output_msg("> Connection Errored");
}));
ws.add_event_listener( enclose!( (output_msg) move |event: SocketCloseEvent| {
output_msg(&format!("> Connection Closed: {}", event.reason()));
}));
ws.add_event_listener( enclose!( (output_msg) move |event: SocketMessageEvent| {
output_msg(&event.data().into_text().unwrap());
}));
let text_entry: InputElement = document().query_selector( ".form input" ).unwrap().unwrap().try_into().unwrap();
text_entry.add_event_listener( enclose!( (text_entry) move |event: KeyPressEvent| {
if event.key() == "Enter" {
event.prevent_default();
let text: String = text_entry.raw_value();
if text.is_empty() == false {
text_entry.set_raw_value("");
ws.send_text(&text).unwrap();
}
}
}));
stdweb::event_loop();
}
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>stdweb • Echo</title>
</head>
<body>
<div class="form">
<input type="text" name="input" id="input" placeholder="Type here">
</div>
<div class="output">
</div>
<script src="echo.js"></script>
</body>
</html>
Example 4: Futures Example
main.rs
#![feature(async_await)]
#[macro_use]
extern crate stdweb;
use futures::future::{join, try_join};
use stdweb::{PromiseFuture, spawn_local, unwrap_future};
use stdweb::web::wait;
use stdweb::web::error::Error;
use stdweb::unstable::TryInto;
// Converts a JavaScript Promise into a Rust Future
fn javascript_promise() -> PromiseFuture< u32 > {
js!(
return new Promise( function ( success, error ) {
setTimeout( function () {
success( 50 );
}, 2000 );
} );
).try_into().unwrap()
}
async fn print( message: &str ) {
// Waits for 2000 milliseconds
wait( 2000 ).await;
console!( log, message );
}
async fn future_main() -> Result< (), Error > {
// Runs Futures synchronously
print( "Hello" ).await;
print( "There" ).await;
{
// Runs multiple Futures in parallel
let ( a, b ) = join(
print( "Test 1" ),
print( "Test 2" ),
).await;
console!( log, "join", a, b );
}
{
// Runs multiple Futures (which can error) in parallel
let ( a, b ) = try_join(
javascript_promise(),
javascript_promise(),
).await?;
console!( log, "try_join", a, b );
}
Ok( () )
}
fn main() {
stdweb::initialize();
spawn_local( unwrap_future( future_main() ) );
stdweb::event_loop();
}
Reference
Read more here.