mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-05-15 15:21:20 +00:00
This commit adds a new attribute to `#[wasm_bindgen]`: `start`. The `start` attribute can be used to indicate that a function should be executed when the module is loaded, configuring the `start` function of the wasm executable. While this doesn't necessarily literally configure the `start` section, it does its best! Only one crate in a crate graph may indicate `#[wasm_bindgen(start)]`, so it's not recommended to be used in libraries but only end-user applications. Currently this still must be used with the `crate-type = ["cdylib"]` annotation in `Cargo.toml`. The implementation here is somewhat tricky because of the circular dependency between our generated JS and the wasm file that we emit. This circular dependency makes running initialization routines (like the `start` shim) particularly fraught with complications because one may need to run before the other but bundlers may not necessarily respect it. Workarounds have been implemented for various emission strategies, for example calling the start function directly after exports are wired up with `--no-modules` and otherwise working around what appears to be a Webpack bug with initializers running in a different order than we'd like. In any case, this in theory doesn't show up to the end user! Closes #74
115 lines
4.2 KiB
Rust
115 lines
4.2 KiB
Rust
extern crate js_sys;
|
|
extern crate wasm_bindgen;
|
|
extern crate web_sys;
|
|
|
|
use js_sys::{Array, Date};
|
|
use wasm_bindgen::prelude::*;
|
|
use wasm_bindgen::JsCast;
|
|
use web_sys::{Document, Element, HtmlElement, Window};
|
|
|
|
#[wasm_bindgen(start)]
|
|
pub fn run() -> Result<(), JsValue> {
|
|
let window = web_sys::window().expect("should have a window in this context");
|
|
let document = window.document().expect("window should have a document");
|
|
|
|
// One of the first interesting things we can do with closures is simply
|
|
// access stack data in Rust!
|
|
let array = Array::new();
|
|
array.push(&"Hello".into());
|
|
array.push(&1.into());
|
|
let mut first_item = None;
|
|
array.for_each(&mut |obj, idx, _arr| match idx {
|
|
0 => {
|
|
assert_eq!(obj, "Hello");
|
|
first_item = obj.as_string();
|
|
}
|
|
1 => assert_eq!(obj, 1),
|
|
_ => panic!("unknown index: {}", idx),
|
|
});
|
|
assert_eq!(first_item, Some("Hello".to_string()));
|
|
|
|
// Below are some more advanced usages of the `Closure` type for closures
|
|
// that need to live beyond our function call.
|
|
|
|
setup_clock(&window, &document)?;
|
|
setup_clicker(&document);
|
|
|
|
// And now that our demo is ready to go let's switch things up so
|
|
// everything is displayed and our loading prompt is hidden.
|
|
document
|
|
.get_element_by_id("loading")
|
|
.expect("should have #loading on the page")
|
|
.dyn_ref::<HtmlElement>()
|
|
.expect("#loading should be an `HtmlElement`")
|
|
.style()
|
|
.set_property("display", "none")?;
|
|
document
|
|
.get_element_by_id("script")
|
|
.expect("should have #script on the page")
|
|
.dyn_ref::<HtmlElement>()
|
|
.expect("#script should be an `HtmlElement`")
|
|
.style()
|
|
.set_property("display", "block")?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Set up a clock on our page and update it each second to ensure it's got
|
|
// an accurate date.
|
|
//
|
|
// Note the usage of `Closure` here because the closure is "long lived",
|
|
// basically meaning it has to persist beyond the call to this one function.
|
|
// Also of note here is the `.as_ref().unchecked_ref()` chain, which is who
|
|
// you can extract `&Function`, what `web-sys` expects, from a `Closure`
|
|
// which only hands you `&JsValue` via `AsRef`.
|
|
fn setup_clock(window: &Window, document: &Document) -> Result<(), JsValue> {
|
|
let current_time = document
|
|
.get_element_by_id("current-time")
|
|
.expect("should have #current-time on the page");
|
|
update_time(¤t_time);
|
|
let a = Closure::wrap(Box::new(move || update_time(¤t_time)) as Box<Fn()>);
|
|
window
|
|
.set_interval_with_callback_and_timeout_and_arguments_0(a.as_ref().unchecked_ref(), 1000)?;
|
|
fn update_time(current_time: &Element) {
|
|
current_time.set_inner_html(&String::from(
|
|
Date::new_0().to_locale_string("en-GB", &JsValue::undefined()),
|
|
));
|
|
}
|
|
|
|
// The instances of `Closure` that we created will invalidate their
|
|
// corresponding JS callback whenever they're dropped, so if we were to
|
|
// normally return from `run` then both of our registered closures will
|
|
// raise exceptions when invoked.
|
|
//
|
|
// Normally we'd store these handles to later get dropped at an appropriate
|
|
// time but for now we want these to be global handlers so we use the
|
|
// `forget` method to drop them without invalidating the closure. Note that
|
|
// this is leaking memory in Rust, so this should be done judiciously!
|
|
a.forget();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// We also want to count the number of times that our green square has been
|
|
// clicked. Our callback will update the `#num-clicks` div.
|
|
//
|
|
// This is pretty similar above, but showing how closures can also implement
|
|
// `FnMut()`.
|
|
fn setup_clicker(document: &Document) {
|
|
let num_clicks = document
|
|
.get_element_by_id("num-clicks")
|
|
.expect("should have #num-clicks on the page");
|
|
let mut clicks = 0;
|
|
let a = Closure::wrap(Box::new(move || {
|
|
clicks += 1;
|
|
num_clicks.set_inner_html(&clicks.to_string());
|
|
}) as Box<FnMut()>);
|
|
document
|
|
.get_element_by_id("green-square")
|
|
.expect("should have #green-square on the page")
|
|
.dyn_ref::<HtmlElement>()
|
|
.expect("#green-square be an `HtmlElement`")
|
|
.set_onclick(Some(a.as_ref().unchecked_ref()));
|
|
a.forget();
|
|
}
|