A standard library for the client-side Web
The goal of this crate is to provide Rust bindings to the Web APIs and to allow a high degree of interoperability between Rust and JavaScript.
Donate
Patrons
This software was brought to you thanks to these wonderful people:
- Ben Berman
Thank you!
Examples
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 );
Even closures are 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." );
};
This crate also exposes a number of Web APIs, for example:
let button = document().query_selector( "#hide-button" ).unwrap();
button.add_event_listener( move |_: ClickEvent| {
for anchor in document().query_selector_all( "#main a" ) {
js!( @{anchor}.style = "display: none;"; );
}
});
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.
Getting started
Take a look at some of the examples:
examples/minimal
- a totally minimal example which calls alertexamples/todomvc
- a naively implemented TodoMVC application; shows how to call into the DOMexamples/hasher
- shows how to export Rust functions to JavaScript and how to call them from the browser or Nodejspinky-web
- an NES emulator; you can play with the precompiled version here
Running the examples
-
Install cargo-web:
$ cargo install -f cargo-web
-
Go into
examples/todomvc
and start the example using one of these commands:-
Compile to WebAssembly using Rust's native WebAssembly backend (requires Rust nightly!):
$ cargo web start --target=wasm32-unknown-unknown
-
Compile to asm.js using Emscripten:
$ cargo web start --target=asmjs-unknown-emscripten
-
Compile to WebAssembly using Emscripten:
$ cargo web start --target=wasm32-unknown-emscripten
-
-
Visit
http://localhost:8000
with your browser.
For the *-emscripten
targets cargo-web
is not necessary, however
the native wasm32-unknown-unknown
which doesn't need Emscripten
requires cargo-web
to work!
Exposing Rust functions to JavaScript
WARNING: This is only supported for Rust's native wasm32-unknown-unknown
target
and requires Rust nightly!
(Note: this is based on the examples/hasher
example)
With the stdweb
crate you can easily expose a Rust function
to JavaScript like this:
#[macro_use]
extern crate stdweb;
extern crate sha1;
use sha1::Sha1;
fn hash( string: String ) -> String {
let mut hasher = Sha1::new();
hasher.update( string.as_bytes() );
hasher.digest().to_string()
}
fn main() {
stdweb::initialize();
js! {
Module.exports.sha1 = @{hash};
}
}
If you compile this code with cargo-web build --target=wasm32-unknown-unknown
you'll get two files:
target/wasm32-unknown-unknown/release/hasher.js
target/wasm32-unknown-unknown/release/hasher.wasm
You can copy them into your JavaScript project and load like any other JavaScript file:
<script src="hasher.js"></script>
After it's loaded you can access Rust.hasher
, which is a Promise which
will resolve once the WebAssembly module is loaded. Inside that promise
you'll find the contents of Module.exports
which we've set from our
Rust code, which includes our exported function which you can now call:
<script>
Rust.hasher.then( function( hasher ) {
const string = "fiddlesticks";
const hash = hasher.sha1( string );
console.log( "Hash of " + string + " is '" + hash + "'" );
});
</script>
You can also use the very same hasher.js
from Nodejs:
const hasher = require( "hasher.js" );
const string = "fiddlesticks";
const hash = hasher.sha1( string );
console.log( "Hash of " + string + " is '" + hash + "'" );
For the Nodejs environment the WebAssembly is compiled synchronously.
Parcel plugin
There is also an experimental Parcel plugin here.
Changelog
-
0.4
- (breaking change) Removed
Array
andObject
variants fromValue
; these are now treated asReference
s - (breaking change) Removed:
InputElement::set_kind
InputElement::files
- (breaking change) Renamed:
KeydownEvent
->KeyDownEvent
KeyupEvent
->KeyUpEvent
KeypressEvent
->KeyPressEvent
ReadyState
->FileReaderReadyState
InputElement::value
->InputElement::raw_value
InputElement::set_value
->InputElement::set_raw_value
- (breaking change)
ArrayBuffer::new
now takes anu64
argument - (breaking change)
InputElement::set_raw_value
now takes&str
instead ofInto< Value >
- (breaking change) Changed return types:
INode::remove_child
now returnsNode
in theOk
case- The following now return an
u64
:ArrayBuffer::len
- The following now return an
i32
instead off64
:IMouseEvent::client_x
IMouseEvent::client_y
IMouseEvent::movement_x
IMouseEvent::movement_y
IMouseEvent::screen_x
IMouseEvent::screen_y
- The following now return a
Result
:INode::insert_before
INode::replace_child
INode::clone_node
StringMap::insert
TokenList::add
TokenList::remove
Document::create_element
IEventTarget::dispatch_event
FileReader::read_as_text
FileReader::read_as_array_buffer
FileReader::read_as_text
History::replace_state
History::go
History::back
History::forward
Location::href
Location::hash
CanvasElement::to_data_url
CanvasElement::to_blob
ArrayBuffer::new
INode::base_uri
now returns aString
instead ofOption< String >
InputElement::raw_value
now returns aString
instead ofValue
- (breaking change)
INode::inner_text
was moved toIHtmlElement::inner_text
- (breaking change)
Document::query_selector
andDocument::query_selector_all
were moved toIParentNode
- (breaking change)
IElement::query_selector
andIElement::query_selector_all
were moved toIParentNode
- (breaking change) A blanket impl for converting between arbitrary reference-like objects using
TryFrom
/TryInto
has been removed - When building using a recent
cargo-web
it's not necessary to callstdweb::initialize
norstdweb::event_loop
anymore - Support for
cdylib
crates onwasm32-unknown-unknown
- New bindings:
XmlHttpRequest
WebSocket
MutationObserver
History
TextAreaElement
CanvasElement
- New event types:
MouseDownEvent
MouseUpEvent
MouseMoveEvent
PopStateEvent
ResizeEvent
ReadyStateChange
SocketCloseEvent
SocketErrorEvent
SocketOpenEvent
SocketMessageEvent
- Initial support for the Canvas APIs
- New traits:
ReferenceType
andInstanceOf
- Add
#[derive(ReferenceType)]
instdweb-derive
crate; it's now possible to define custom API bindings outside ofstdweb
- Add
#[js_export]
procedural attribute (wasm32-unknown-unknown
only) - Add
DomException
and subtypes for passing around JavaScript exceptions IElement
now inherits fromINode
- Every interface now inherits from
ReferenceType
- (breaking change) Removed
-
0.3
- (breaking change) Deleted
ErrorEvent
methods - (breaking change) Renamed:
LoadEvent
->ResourceLoadEvent
AbortEvent
->ResourceAbortEvent
ErrorEvent
->ResourceErrorEvent
- Add
UnsafeTypedArray
for zero cost slice passing tojs!
- Add
Once
for passingFnOnce
closures tojs!
- (breaking change) Deleted
License
Licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Snippets of documentation which come from Mozilla Developer Network are covered under the CC-BY-SA, version 2.5 or later.
Contributing
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
You can run stdweb
's tests with cargo web test --features web_test
, which will
run them under headless Chromium.