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.

Categorized in: